feat: Add CREATE NAMESPACE to group GraphQL queries and mutations#2187
feat: Add CREATE NAMESPACE to group GraphQL queries and mutations#2187velo wants to merge 1 commit into
Conversation
Signed-off-by: Marvin Froeder <marvin@datasqrl.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #2187 +/- ##
============================================
- Coverage 16.39% 16.24% -0.16%
Complexity 1037 1037
============================================
Files 618 620 +2
Lines 17911 18089 +178
Branches 2189 2215 +26
============================================
+ Hits 2937 2939 +2
- Misses 14676 14851 +175
- Partials 298 299 +1 ☔ View full report in Codecov by Harness. |
|
This is a really nice idea, thanks @velo. I am not sure about the nesting syntax within GraphQL. This is definitely an accepted practice, but it feels a bit hacky to me, since you are using a type (which is supposed to be part of the entity model) to group query endpoints. I can see the benefit from a human user (all "admin" endpoints are in one place) but also downsides (to find out what the admin ednpoints are I need to navigate to the type) and wonder if the benefits still matter a lot in a world where AI does most of this low level coding. Hence, I'm wondering if we can separate the code reuse from the presentation level concerns:
Meaning, we could make 2. something that works by convention when the user supplies the GraphQL schema, which I think is what this PR proposes for mutations. We allow the same for subscriptions. Something like: we map queries/mutations which have this type of nesting to functions/mutation tables by concatenating the components with For 1, I'm wondering if we can keep things aligned with the existing SQL syntax and convention by supporting something like this: CREATE TABLE _MyBackendAuth (
authDataGroupIds ARRAY<String> NOT NULL METADATA FROM 'auth.https://datasqrl.com/data_group_ids'
);
CREATE TABLE _MyBackendFunctionAuth (
impersonateTenantId String
) LIKE _MyBackendAuth;
CREATE TABLE SomeBackendMutation (
....
) LIKE _MyBackendAuth;
backend_SomeFunction(arg String) LIKE _MyBackendFunctionAuth := SELECT ...;This has the benefit that:
Just throwing out some ideas. |
|
My concern was introducing the |
That is a pretty common industry practice. Shopify's, GitLab, and WPGraphQL do that. And I like the flexibility it gave me, like the ability to impersonate a tenantId (installationId, dataGroupId, whatever our hearts desire), that is extremely powerful on troubleshooting session.
Honestly, my main motivation on this change was to group queries without prefix. With AI taking over user attention is likely more thin, and having things grouped when they are related, most likely make devs life easier when they need to review something. and having prefix on endpoint is one more thing to keep in our limited heads =) I need to filter out the Anyways, my goal was to get a better graphql syntax. The changes on sqrl were just to accommodate that. Maybe use the leading to: the Another alternative, would be allowing qualified tables What do you think? |
|
Also AI said:
|
|
Just brainstorming here: One thing we could do is treat Behind the scenes we would treat queryspace like a "normal" table that doesn't get exposed as a mutation but as a query groupging:
That way, the changes to SQRL are pretty minimal. And, most importantly, the mental model still works: tables map to types and WDYT @velo @ferenc-csaky |
What
Adds
CREATE NAMESPACE— a single construct that groups GraphQL queries/mutations under a sub-object (e.g.backend { ... }/admin { ... }) and declares parameters shared by every function in the namespace. Replaces the convention of long name prefixes (backendFoo,adminBar) and per-function repetition of the same auth columns.produces:
METADATA FROM 'auth....'is a hidden JWT claim; a param without it becomes an argument exposed on the namespace field itself.<name>.funcpath prefix and inherit the params automatically; reference them as:<name>.<param>.this.); claims are read from the JWT.How it works (reuses existing machinery)
admin.foo) whose head resolves to no table is a namespace (vs a relationship). NewSqrlTableFunction.namespacedflag.ParentParameteron the sub-function + exposed as args on the namespace field. Metadata params →MetadataParameter. Both hidden from the sub-field.:ns.argfrom it. NewStaticQueryCoords..graphqls(KafkaMutationCoords.parentType). Subscriptions cannot be namespaced (GraphQL requires a single root subscription field); the parser blocks it.Also fixes a latent
GraphQLSchemaConverterbug (missing comma between sibling nested-field variable declarations in generated REST/MCP operations), which corrected several pre-existing snapshots.Tests
dagplanner/namespacedFunctionTest.sqrl+namespace*-fail.sqrl(compile + inferred-schema model).GraphQLSchemaConverterTest,UseCaseCompileTest(namespaced-package) — validates a hand-written namespaced query + mutation schema.usecases/namespaced/end-to-end viaFullUseCaseIT: proves namespace-argument propagation (backend(minTokens: 300)filters the sub-query), a namespaced mutation, and a subscription against real Postgres/Kafka/Flink.Limitation
A namespaced function is access-only, so it cannot be invoked from another function body via
Table(ns.fn(...)). Compositions likefoo := SELECT * FROM Table(fooWithTime(...))need the callee kept as a flat callable helper or inlined.