diff --git a/CLAUDE.md b/CLAUDE.md index a3fd958..b35d5bd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -88,10 +88,14 @@ go test ./parser -run TestCorpus -check-parse 'FRAGMENT' # dump detail for it go test ./parser # gate ``` -Oracle rejects raised by upstream's *transformer* (Parser Errors whose -message is not "syntax error at or near ...") that darkwing's transformer -does not reproduce yet stay in todo metadata with a note saying why; the -harness flags todo entries that start agreeing so they get removed. +Since milestone 5 every statement has a transformer and the upstream +transformer's own Parser Errors are reproduced. The remaining todo +entries cover oracle rejects raised *outside* the parse pipeline — +bind-time validation that happens to throw ParserException (setting +values, function argument checks, PRIMARY KEY verification) and nested +SQL parsing inside functions like query() and nextval() — each with a +note saying why; the harness flags todo entries that start agreeing so +they get removed. Sweeping tree shapes against a live oracle (milestones 3-4 exit criteria: zero mismatches over the corpus SELECT subset): diff --git a/ast/misc.go b/ast/misc.go new file mode 100644 index 0000000..4424cfd --- /dev/null +++ b/ast/misc.go @@ -0,0 +1,582 @@ +package ast + +// misc.go: the milestone-5 statement nodes — DuckDB-native statements +// outside the SELECT/DML/DDL cores: SET/RESET, PRAGMA, CALL, transactions, +// ATTACH/DETACH/USE/CONNECT, COPY, EXPORT/IMPORT, extensions +// (INSTALL/LOAD/UPDATE EXTENSIONS, external resources), CHECKPOINT, +// VACUUM/ANALYZE, COMMENT ON, prepared statements and EXPLAIN. Node names +// follow DuckDB's statement classes; where upstream desugars a statement +// into another family (USE into SET, CHECKPOINT into CALL, IMPORT into +// PRAGMA, DEALLOCATE into DROP, ANALYZE into VACUUM), darkwing keeps a +// dedicated node and notes the upstream shape, for sqlc's benefit. + +// GenericOption is one entry of the COPY/ATTACH/SECRET/CONNECT/EXPLAIN +// option soup. Port of GenericCopyOption: constant-ish arguments are +// folded into Values; anything else stays an expression. +type GenericOption struct { + Name string `json:"name"` + Values []Value `json:"values,omitempty"` + Expr Expr `json:"expression,omitempty"` +} + +// optionExprs collects the expression payloads of an option list for +// Children traversal. +func optionExprs(nodes []Node, opts []GenericOption) []Node { + for _, o := range opts { + nodes = add(nodes, o.Expr) + } + return nodes +} + +// SetScope mirrors DuckDB's SetScope spelling. +type SetScope string + +const ( + SetScopeAutomatic SetScope = "AUTOMATIC" + SetScopeLocal SetScope = "LOCAL" + SetScopeSession SetScope = "SESSION" + SetScopeGlobal SetScope = "GLOBAL" + SetScopeVariable SetScope = "VARIABLE" +) + +// SetStatement is SET = (and the SET SCHEMA / SET TIME +// ZONE special forms). Port of SetVariableStatement. +type SetStatement struct { + stmtBase + Name string `json:"name"` + Scope SetScope `json:"scope"` + Value Expr `json:"value"` + NamedParams []NamedParam `json:"named_param_map,omitempty"` +} + +func (s *SetStatement) Children() []Node { return add(nil, s.Value) } + +// ResetStatement is RESET (and SET ... TO DEFAULT / SET TIME +// ZONE LOCAL, which upstream folds into resets). Port of +// ResetVariableStatement. +type ResetStatement struct { + stmtBase + Name string `json:"name"` + Scope SetScope `json:"scope"` +} + +func (s *ResetStatement) Children() []Node { return nil } + +// NamedValue is a name/expression pair (PRAGMA named parameters, EXECUTE +// arguments). +type NamedValue struct { + Name string `json:"name"` + Expr Expr `json:"expression"` +} + +// PragmaStatement is PRAGMA (...) — the call form, plus the +// assignment forms upstream keeps as pragmas (SQLite-compatible pragmas, +// IMPORT DATABASE and COPY FROM DATABASE desugarings). Port of +// PragmaStatement/PragmaInfo. +type PragmaStatement struct { + stmtBase + Name string `json:"name"` + Parameters []Expr `json:"parameters,omitempty"` + NamedParameters []NamedValue `json:"named_parameters,omitempty"` + NamedParams []NamedParam `json:"named_param_map,omitempty"` +} + +func (s *PragmaStatement) Children() []Node { + nodes := exprs(s.Parameters) + for _, nv := range s.NamedParameters { + nodes = add(nodes, nv.Expr) + } + return nodes +} + +// CallStatement is CALL fn(...). Port of CallStatement. +type CallStatement struct { + stmtBase + Function *FunctionExpression `json:"function"` + NamedParams []NamedParam `json:"named_param_map,omitempty"` +} + +func (s *CallStatement) Children() []Node { return add(nil, s.Function) } + +// UseStatement is USE db / USE db.schema. Upstream desugars it into +// SET schema; darkwing keeps the target. A single name is recorded in +// Name, USE db.schema fills Catalog and Name. +type UseStatement struct { + stmtBase + Catalog string `json:"catalog,omitempty"` + Name string `json:"name"` +} + +func (s *UseStatement) Children() []Node { return nil } + +// CheckpointStatement is [FORCE] CHECKPOINT [catalog]. Upstream desugars +// it into CALL checkpoint(...) / force_checkpoint(...). +type CheckpointStatement struct { + stmtBase + Force bool `json:"force,omitempty"` + Catalog string `json:"catalog,omitempty"` +} + +func (s *CheckpointStatement) Children() []Node { return nil } + +// TransactionKind mirrors DuckDB's TransactionType spelling. +type TransactionKind string + +const ( + TransactionBegin TransactionKind = "BEGIN_TRANSACTION" + TransactionCommit TransactionKind = "COMMIT" + TransactionRollback TransactionKind = "ROLLBACK" +) + +// TransactionModifier mirrors DuckDB's TransactionModifierType. +type TransactionModifier string + +const ( + TransactionModifierNone TransactionModifier = "" + TransactionReadOnly TransactionModifier = "TRANSACTION_READ_ONLY" + TransactionReadWrite TransactionModifier = "TRANSACTION_READ_WRITE" +) + +// TransactionStatement is BEGIN/COMMIT/ROLLBACK (with the START/END/ABORT +// spellings). Port of TransactionStatement/TransactionInfo. +type TransactionStatement struct { + stmtBase + Kind TransactionKind `json:"kind"` + Modifier TransactionModifier `json:"modifier,omitempty"` +} + +func (s *TransactionStatement) Children() []Node { return nil } + +// VacuumStatement is VACUUM [options] [table [(columns)]]. Port of +// VacuumStatement/VacuumInfo; upstream errors NotImplemented on +// FULL/FREEZE/VERBOSE after parsing, so darkwing records the raw options +// and accepts. +type VacuumStatement struct { + stmtBase + Vacuum bool `json:"vacuum,omitempty"` + Analyze bool `json:"analyze,omitempty"` + // Options is the raw option spelling list ("full", "freeze", ...). + Options []string `json:"options,omitempty"` + Table TableRef `json:"table,omitempty"` + Columns []string `json:"columns,omitempty"` +} + +func (s *VacuumStatement) Children() []Node { return add(nil, s.Table) } + +// AnalyzeStatement is ANALYZE [VERBOSE] [table [(columns)]]. Upstream +// desugars it into a VacuumStatement with the analyze option. +type AnalyzeStatement struct { + stmtBase + Verbose bool `json:"verbose,omitempty"` + Table TableRef `json:"table,omitempty"` + Columns []string `json:"columns,omitempty"` +} + +func (s *AnalyzeStatement) Children() []Node { return add(nil, s.Table) } + +// ExportStatement is EXPORT DATABASE [db TO] 'path' (options). Port of +// ExportStatement (whose payload is a CopyInfo; darkwing keeps the +// relevant fields flat). +type ExportStatement struct { + stmtBase + Database string `json:"database,omitempty"` + Path string `json:"path"` + // Format defaults to csv like upstream. + Format string `json:"format"` + Options []GenericOption `json:"options,omitempty"` + NamedParams []NamedParam `json:"named_param_map,omitempty"` +} + +func (s *ExportStatement) Children() []Node { return optionExprs(nil, s.Options) } + +// ImportStatement is IMPORT DATABASE 'path'. Upstream desugars it into +// PRAGMA import_database('path'). +type ImportStatement struct { + stmtBase + Path string `json:"path"` +} + +func (s *ImportStatement) Children() []Node { return nil } + +// DeallocateStatement is DEALLOCATE [PREPARE] name. Upstream desugars it +// into DROP PREPARED_STATEMENT. +type DeallocateStatement struct { + stmtBase + Name string `json:"name"` +} + +func (s *DeallocateStatement) Children() []Node { return nil } + +// PrepareStatement is PREPARE name AS . Port of +// PrepareStatement. +type PrepareStatement struct { + stmtBase + Name string `json:"name"` + Statement Stmt `json:"statement"` +} + +func (s *PrepareStatement) Children() []Node { return add(nil, s.Statement) } + +// ExecuteStatement is EXECUTE name(args). Port of ExecuteStatement: +// positional arguments are named by their 1-based position, mirroring +// upstream's named_values map (kept ordered here). +type ExecuteStatement struct { + stmtBase + Name string `json:"name"` + Values []NamedValue `json:"named_values,omitempty"` + NamedParams []NamedParam `json:"named_param_map,omitempty"` +} + +func (s *ExecuteStatement) Children() []Node { + var nodes []Node + for _, v := range s.Values { + nodes = add(nodes, v.Expr) + } + return nodes +} + +// ExplainType mirrors DuckDB's ExplainType. +type ExplainType string + +const ( + ExplainStandard ExplainType = "EXPLAIN_STANDARD" + ExplainAnalyze ExplainType = "EXPLAIN_ANALYZE" +) + +// ExplainStatement is EXPLAIN [ANALYZE] [(options)] . Port of +// ExplainStatement. +type ExplainStatement struct { + stmtBase + Statement Stmt `json:"statement"` + Type ExplainType `json:"explain_type"` + // Format is the FORMAT option value (lowercased), empty for default. + Format string `json:"format,omitempty"` + NamedParams []NamedParam `json:"named_param_map,omitempty"` +} + +func (s *ExplainStatement) Children() []Node { return add(nil, s.Statement) } + +// AttachStatement is ATTACH [DATABASE] 'path' [AS alias] [(options)]. +// Port of AttachStatement/AttachInfo. +type AttachStatement struct { + stmtBase + // Path is the database path expression (usually a string constant). + Path Expr `json:"path"` + Alias string `json:"alias,omitempty"` + OnConflict OnCreateConflict `json:"on_conflict"` + Options []GenericOption `json:"options,omitempty"` + NamedParams []NamedParam `json:"named_param_map,omitempty"` +} + +func (s *AttachStatement) Children() []Node { return optionExprs(add(nil, s.Path), s.Options) } + +// DetachStatement is DETACH [DATABASE] [IF EXISTS] name. Port of +// DetachStatement/DetachInfo. +type DetachStatement struct { + stmtBase + Name string `json:"name"` + IfExists bool `json:"if_exists,omitempty"` +} + +func (s *DetachStatement) Children() []Node { return nil } + +// ConnectStatement is CONNECT [LOCAL | 'target' (options) | catalog]. +// Port of ConnectStatement/ConnectInfo. +type ConnectStatement struct { + stmtBase + // Local is CONNECT LOCAL. + Local bool `json:"local,omitempty"` + Name string `json:"name,omitempty"` + // NameIsString marks a string-literal target. + NameIsString bool `json:"name_is_string,omitempty"` + Options []GenericOption `json:"options,omitempty"` + NamedParams []NamedParam `json:"named_param_map,omitempty"` +} + +func (s *ConnectStatement) Children() []Node { return optionExprs(nil, s.Options) } + +// DisconnectStatement is DISCONNECT. Port of DisconnectStatement. +type DisconnectStatement struct { + stmtBase +} + +func (s *DisconnectStatement) Children() []Node { return nil } + +// LoadKind mirrors DuckDB's LoadType. +type LoadKind string + +const ( + LoadTypeLoad LoadKind = "LOAD" + LoadTypeLoadAs LoadKind = "LOAD_AS" + LoadTypeInstall LoadKind = "INSTALL" + LoadTypeForceInstall LoadKind = "FORCE_INSTALL" +) + +// LoadStatement is LOAD / INSTALL / FORCE INSTALL. Port of +// LoadStatement/LoadInfo (upstream models INSTALL as a load kind). +type LoadStatement struct { + stmtBase + Kind LoadKind `json:"kind"` + // Name is the extension name or filename. + Name string `json:"name"` + // Alias is LOAD ... AS alias. + Alias string `json:"alias,omitempty"` + // Repository is INSTALL ... FROM ; RepoIsAlias marks the + // identifier (alias) form as opposed to a literal URL. + Repository string `json:"repository,omitempty"` + RepoIsAlias bool `json:"repo_is_alias,omitempty"` + Version string `json:"version,omitempty"` +} + +func (s *LoadStatement) Children() []Node { return nil } + +// UpdateExtensionsStatement is UPDATE EXTENSIONS [(names)]. Port of +// UpdateExtensionsStatement. +type UpdateExtensionsStatement struct { + stmtBase + Extensions []string `json:"extensions,omitempty"` +} + +func (s *UpdateExtensionsStatement) Children() []Node { return nil } + +// ExternalResourceOperation mirrors DuckDB's ExternalResourceOperation. +type ExternalResourceOperation string + +const ( + ExternalResourceCreate ExternalResourceOperation = "CREATE" + ExternalResourceRegister ExternalResourceOperation = "REGISTER" + ExternalResourceDestroy ExternalResourceOperation = "DESTROY" + ExternalResourceShow ExternalResourceOperation = "SHOW" +) + +// ExternalResourceStatement is CREATE/REGISTER/DESTROY EXTERNAL RESOURCE +// and SHOW [ALL] EXTERNAL RESOURCES. Port of ExternalResourceStatement. +type ExternalResourceStatement struct { + stmtBase + Operation ExternalResourceOperation `json:"operation"` + // Type is the resource type string literal of CREATE/REGISTER. + Type string `json:"type,omitempty"` + Name string `json:"name,omitempty"` + // Handle is the REGISTER ... FROM expression. + Handle Expr `json:"handle,omitempty"` + Options []GenericOption `json:"options,omitempty"` + // All is SHOW ALL EXTERNAL RESOURCES. + All bool `json:"all,omitempty"` + NamedParams []NamedParam `json:"named_param_map,omitempty"` +} + +func (s *ExternalResourceStatement) Children() []Node { + return optionExprs(add(nil, s.Handle), s.Options) +} + +// CommentOnType names the object family a COMMENT ON statement targets. +type CommentOnType string + +const ( + CommentOnTable CommentOnType = "TABLE" + CommentOnSequence CommentOnType = "SEQUENCE" + CommentOnMacro CommentOnType = "MACRO" + CommentOnMacroTable CommentOnType = "MACRO_TABLE" + CommentOnView CommentOnType = "VIEW" + CommentOnDatabase CommentOnType = "DATABASE" + CommentOnIndex CommentOnType = "INDEX" + CommentOnSchema CommentOnType = "SCHEMA" + CommentOnTypeEntry CommentOnType = "TYPE" + CommentOnColumn CommentOnType = "COLUMN" +) + +// CommentOnStatement is COMMENT ON IS . Upstream +// desugars it into ALTER with a SetCommentInfo; darkwing keeps the +// statement. For COMMENT ON COLUMN the column name is split into Column +// and the table path fills Catalog/Schema/Name. +type CommentOnStatement struct { + stmtBase + OnType CommentOnType `json:"on_type"` + Catalog string `json:"catalog,omitempty"` + Schema string `json:"schema,omitempty"` + Name string `json:"name"` + Column string `json:"column,omitempty"` + // Value is the comment constant: a string or NULL. + Value Expr `json:"value"` +} + +func (s *CommentOnStatement) Children() []Node { return add(nil, s.Value) } + +// CopyStatement is COPY table/select TO/FROM file. Port of +// CopyStatement/CopyInfo. +type CopyStatement struct { + stmtBase + Info *CopyInfo `json:"info"` + NamedParams []NamedParam `json:"named_param_map,omitempty"` +} + +func (s *CopyStatement) Children() []Node { return add(nil, s.Info) } + +// CopyInfo is the payload of COPY (and, upstream, EXPORT). Port of +// CopyInfo. +type CopyInfo struct { + spanned + Catalog string `json:"catalog,omitempty"` + Schema string `json:"schema,omitempty"` + Table string `json:"table,omitempty"` + // Columns is the copied column list. + Columns []string `json:"select_list,omitempty"` + // IsFrom is COPY ... FROM (true) vs COPY ... TO (false). + IsFrom bool `json:"is_from,omitempty"` + // FilePath is the literal file name; FilePathExpr is set instead when + // the file name is a non-constant expression. + FilePath string `json:"file_path,omitempty"` + FilePathExpr Expr `json:"file_path_expression,omitempty"` + // Format is the explicit or extension-derived format; empty means + // auto-detected downstream. + Format string `json:"format,omitempty"` + // FormatExplicit marks a FORMAT option (upstream's + // !is_format_auto_detected). + FormatExplicit bool `json:"format_explicit,omitempty"` + Options []GenericOption `json:"options,omitempty"` + // Query is the COPY (SELECT ...) TO source. + Query QueryNode `json:"select_statement,omitempty"` +} + +func (i *CopyInfo) Children() []Node { + nodes := add(nil, i.FilePathExpr) + nodes = optionExprs(nodes, i.Options) + return add(nodes, i.Query) +} + +// CopyDatabaseType mirrors DuckDB's CopyDatabaseType. +type CopyDatabaseType string + +const ( + CopyDatabaseSchema CopyDatabaseType = "COPY_SCHEMA" + CopyDatabaseData CopyDatabaseType = "COPY_DATA" +) + +// CopyDatabaseStatement is COPY FROM DATABASE a TO b (SCHEMA|DATA). Port +// of CopyDatabaseStatement; the flagless form desugars into PRAGMA +// copy_database upstream and here. +type CopyDatabaseStatement struct { + stmtBase + FromDatabase string `json:"from_database"` + ToDatabase string `json:"to_database"` + CopyType CopyDatabaseType `json:"copy_type"` +} + +func (s *CopyDatabaseStatement) Children() []Node { return nil } + +// ---- CREATE MACRO / SECRET / TRIGGER payloads -------------------------- + +// MacroParameter is one macro parameter: a name, an optional type, and an +// optional default (`x := expr`). +type MacroParameter struct { + Name string `json:"name"` + Type *TypeExpression `json:"type,omitempty"` + Default Expr `json:"default,omitempty"` +} + +// MacroFunction is one AS (...) definition of CREATE MACRO: a scalar +// expression or a TABLE query. Port of ScalarMacroFunction / +// TableMacroFunction. +type MacroFunction struct { + spanned + Parameters []MacroParameter `json:"parameters"` + // Expr is the scalar macro body; Query is the table macro body. + Expr Expr `json:"expression,omitempty"` + Query *SelectStatement `json:"query,omitempty"` +} + +func (m *MacroFunction) Children() []Node { + var nodes []Node + for i := range m.Parameters { + if m.Parameters[i].Type != nil { + nodes = add(nodes, m.Parameters[i].Type) + } + nodes = add(nodes, m.Parameters[i].Default) + } + return add(nodes, m.Expr, m.Query) +} + +// CreateMacroInfo is CREATE MACRO / FUNCTION. Port of CreateMacroInfo. +type CreateMacroInfo struct { + CreateInfoBase + // IsTable marks table macros (every definition is AS TABLE). + IsTable bool `json:"is_table,omitempty"` + Macros []*MacroFunction `json:"macros"` +} + +func (i *CreateMacroInfo) Children() []Node { + var nodes []Node + for _, m := range i.Macros { + nodes = add(nodes, m) + } + return nodes +} + +// CreateSecretInfo is CREATE SECRET. Port of CreateSecretInfo: TYPE, +// PROVIDER and SCOPE are pulled out of the option list, the rest stays in +// Options. +type CreateSecretInfo struct { + CreateInfoBase + // Storage is the IN specifier (lowercased). + Storage string `json:"storage,omitempty"` + Type Expr `json:"type,omitempty"` + Provider Expr `json:"provider,omitempty"` + Scope Expr `json:"scope,omitempty"` + Options []GenericOption `json:"options,omitempty"` +} + +func (i *CreateSecretInfo) Children() []Node { + return optionExprs(add(nil, i.Type, i.Provider, i.Scope), i.Options) +} + +// TriggerTiming mirrors DuckDB's TriggerTiming. +type TriggerTiming string + +const ( + TriggerBefore TriggerTiming = "BEFORE" + TriggerAfter TriggerTiming = "AFTER" + TriggerInsteadOf TriggerTiming = "INSTEAD_OF" +) + +// TriggerEvent mirrors DuckDB's TriggerEventType. +type TriggerEvent string + +const ( + TriggerEventInsert TriggerEvent = "INSERT" + TriggerEventDelete TriggerEvent = "DELETE" + TriggerEventUpdate TriggerEvent = "UPDATE" +) + +// TriggerForEach mirrors DuckDB's TriggerForEach. +type TriggerForEach string + +const ( + TriggerForEachRow TriggerForEach = "ROW" + TriggerForEachStatement TriggerForEach = "STATEMENT" +) + +// CreateTriggerInfo is CREATE TRIGGER. Port of CreateTriggerInfo. +type CreateTriggerInfo struct { + CreateInfoBase + Timing TriggerTiming `json:"timing"` + Event TriggerEvent `json:"event"` + // Columns is UPDATE OF (columns). + Columns []string `json:"columns,omitempty"` + // Table is the ON target. + Table *BaseTableRef `json:"table"` + // ReferencingNew/Old are the REFERENCING NEW/OLD TABLE AS aliases. + ReferencingNew string `json:"referencing_new_table,omitempty"` + ReferencingOld string `json:"referencing_old_table,omitempty"` + // ForEach is empty when no FOR EACH clause is given. + ForEach TriggerForEach `json:"for_each,omitempty"` + // Body is the trigger action (INSERT/UPDATE/DELETE/MERGE). + Body Stmt `json:"body"` +} + +func (i *CreateTriggerInfo) Children() []Node { + var nodes []Node + if i.Table != nil { + nodes = add(nodes, i.Table) + } + return add(nodes, i.Body) +} diff --git a/ast/query.go b/ast/query.go index a4ef12f..db2a695 100644 --- a/ast/query.go +++ b/ast/query.go @@ -64,6 +64,15 @@ type DeleteQueryNode struct { func (n *DeleteQueryNode) Children() []Node { return add(n.baseChildren(), n.Delete) } +// CopyQueryNode wraps a COPY ... TO used as a CTE body (upstream's +// COPY_QUERY_NODE, new in DuckDB 2.0). +type CopyQueryNode struct { + queryNodeBase + Copy *CopyInfo `json:"copy"` +} + +func (n *CopyQueryNode) Children() []Node { return add(n.baseChildren(), n.Copy) } + // queryNodeBase carries what every DuckDB QueryNode has: result modifiers // and a CTE map. type queryNodeBase struct { diff --git a/parser/dml_test.go b/parser/dml_test.go index cfe5f07..2aa6db1 100644 --- a/parser/dml_test.go +++ b/parser/dml_test.go @@ -198,8 +198,13 @@ func TestDMLParameters(t *testing.T) { if !reflect.DeepEqual(ins.NamedParams, want) { t.Errorf("params = %+v, want %+v", ins.NamedParams, want) } - if msg := parseErr(t, "UPDATE t SET a = $x WHERE b = ?"); !strings.Contains(msg, "Mixing named and positional") { - t.Errorf("mixed params error = %q", msg) + // mixing named and positional parameters is a NotImplemented error + // upstream (post-parse for the oracle), so darkwing accepts and keeps + // numbering + upd := parseOne(t, "UPDATE t SET a = $x WHERE b = ?").(*ast.UpdateStatement) + want = []ast.NamedParam{{Name: "x", Index: 1}, {Name: "2", Index: 2}} + if !reflect.DeepEqual(upd.NamedParams, want) { + t.Errorf("mixed params = %+v, want %+v", upd.NamedParams, want) } } diff --git a/parser/misc_test.go b/parser/misc_test.go new file mode 100644 index 0000000..0db456b --- /dev/null +++ b/parser/misc_test.go @@ -0,0 +1,471 @@ +// misc_test.go: hand-written shape tests for the milestone-5 statement +// transformers — the snapshot coverage for statements json_serialize_sql +// cannot see (it serializes SELECTs only). +package parser + +import ( + "reflect" + "strings" + "testing" + + "github.com/sqlc-dev/darkwing/ast" +) + +func parseAs[T ast.Stmt](t *testing.T, sql string) T { + t.Helper() + stmt := parseOne(t, sql) + typed, ok := stmt.(T) + if !ok { + t.Fatalf("Parse(%q) = %T, want %T", sql, stmt, *new(T)) + } + return typed +} + +func TestSetResetShapes(t *testing.T) { + set := parseAs[*ast.SetStatement](t, "SET memory_limit = '1GB'") + if set.Name != "memory_limit" || set.Scope != ast.SetScopeAutomatic { + t.Errorf("set = %+v", set) + } + if c, ok := set.Value.(*ast.ConstantExpression); !ok || c.Value.Str != "1GB" { + t.Errorf("value = %#v", set.Value) + } + set = parseAs[*ast.SetStatement](t, "SET GLOBAL threads TO 4") + if set.Scope != ast.SetScopeGlobal { + t.Errorf("scope = %q", set.Scope) + } + // a bare identifier value folds to its name + set = parseAs[*ast.SetStatement](t, "SET default_null_order = nulls_first") + if c, ok := set.Value.(*ast.ConstantExpression); !ok || c.Value.Str != "nulls_first" { + t.Errorf("identifier value = %#v", set.Value) + } + // SET SCHEMA and SET TIME ZONE special forms + set = parseAs[*ast.SetStatement](t, "SET SCHEMA 'main'") + if set.Name != "schema" { + t.Errorf("schema set = %+v", set) + } + set = parseAs[*ast.SetStatement](t, "SET TIME ZONE 'UTC'") + if set.Name != "timezone" { + t.Errorf("timezone set = %+v", set) + } + // DEFAULT values fold into resets + if _, ok := parseOne(t, "SET threads TO DEFAULT").(*ast.ResetStatement); !ok { + t.Error("SET ... TO DEFAULT did not become a RESET") + } + if _, ok := parseOne(t, "SET TIME ZONE LOCAL").(*ast.ResetStatement); !ok { + t.Error("SET TIME ZONE LOCAL did not become a RESET") + } + reset := parseAs[*ast.ResetStatement](t, "RESET SESSION threads") + if reset.Name != "threads" || reset.Scope != ast.SetScopeSession { + t.Errorf("reset = %+v", reset) + } + if msg := parseErr(t, "SET a = 1, 2"); !strings.Contains(msg, "single value") { + t.Errorf("multi-value SET error = %q", msg) + } + // VARIABLE scope + set = parseAs[*ast.SetStatement](t, "SET VARIABLE x = 42") + if set.Scope != ast.SetScopeVariable { + t.Errorf("variable scope = %q", set.Scope) + } +} + +func TestPragmaShapes(t *testing.T) { + p := parseAs[*ast.PragmaStatement](t, "PRAGMA enable_progress_bar") + if p.Name != "enable_progress_bar" || len(p.Parameters) != 0 { + t.Errorf("pragma = %+v", p) + } + p = parseAs[*ast.PragmaStatement](t, "PRAGMA table_info('t')") + if len(p.Parameters) != 1 { + t.Errorf("call pragma = %+v", p) + } + // named parameters split off + p = parseAs[*ast.PragmaStatement](t, "PRAGMA database_size(mode = 'fast')") + if len(p.NamedParameters) != 1 || p.NamedParameters[0].Name != "mode" { + t.Errorf("named parameters = %+v", p.NamedParameters) + } + // assignment pragmas become SET statements, except SQLite-compat ones + if _, ok := parseOne(t, "PRAGMA memory_limit='1GB'").(*ast.SetStatement); !ok { + t.Error("assignment pragma did not become SET") + } + p = parseAs[*ast.PragmaStatement](t, "PRAGMA table_info='t'") + if p.Name != "table_info" || len(p.Parameters) != 1 { + t.Errorf("sqlite-compat pragma = %+v", p) + } +} + +func TestCallUseCheckpointShapes(t *testing.T) { + call := parseAs[*ast.CallStatement](t, "CALL pragma_table_info('t')") + if call.Function == nil || call.Function.FunctionName != "pragma_table_info" || + len(call.Function.Arguments) != 1 { + t.Errorf("call = %+v", call.Function) + } + use := parseAs[*ast.UseStatement](t, "USE db") + if use.Name != "db" || use.Catalog != "" { + t.Errorf("use = %+v", use) + } + use = parseAs[*ast.UseStatement](t, "USE db.main") + if use.Catalog != "db" || use.Name != "main" { + t.Errorf("qualified use = %+v", use) + } + if msg := parseErr(t, "USE a.b.c"); !strings.Contains(msg, "USE database") { + t.Errorf("three-part USE error = %q", msg) + } + cp := parseAs[*ast.CheckpointStatement](t, "FORCE CHECKPOINT db") + if !cp.Force || cp.Catalog != "db" { + t.Errorf("checkpoint = %+v", cp) + } +} + +func TestTransactionShapes(t *testing.T) { + tx := parseAs[*ast.TransactionStatement](t, "BEGIN TRANSACTION READ ONLY") + if tx.Kind != ast.TransactionBegin || tx.Modifier != ast.TransactionReadOnly { + t.Errorf("begin = %+v", tx) + } + if tx = parseAs[*ast.TransactionStatement](t, "COMMIT"); tx.Kind != ast.TransactionCommit { + t.Errorf("commit = %+v", tx) + } + if tx = parseAs[*ast.TransactionStatement](t, "ABORT"); tx.Kind != ast.TransactionRollback { + t.Errorf("abort = %+v", tx) + } +} + +func TestVacuumAnalyzeShapes(t *testing.T) { + v := parseAs[*ast.VacuumStatement](t, "VACUUM ANALYZE t(a, b)") + if !v.Vacuum || !v.Analyze { + t.Errorf("vacuum flags = %+v", v) + } + if ref, ok := v.Table.(*ast.BaseTableRef); !ok || ref.TableName != "t" || + !reflect.DeepEqual(v.Columns, []string{"a", "b"}) { + t.Errorf("vacuum target = %#v %v", v.Table, v.Columns) + } + // FULL parses (upstream errors post-parse), recorded as a raw option + v = parseAs[*ast.VacuumStatement](t, "VACUUM FULL") + if !reflect.DeepEqual(v.Options, []string{"full"}) { + t.Errorf("legacy options = %v", v.Options) + } + a := parseAs[*ast.AnalyzeStatement](t, "ANALYZE t") + if ref, ok := a.Table.(*ast.BaseTableRef); !ok || ref.TableName != "t" { + t.Errorf("analyze target = %#v", a.Table) + } +} + +func TestExportImportShapes(t *testing.T) { + e := parseAs[*ast.ExportStatement](t, "EXPORT DATABASE db TO 'dir' (FORMAT parquet)") + if e.Database != "db" || e.Path != "dir" || e.Format != "parquet" || len(e.Options) != 0 { + t.Errorf("export = %+v", e) + } + e = parseAs[*ast.ExportStatement](t, "EXPORT DATABASE 'dir'") + if e.Format != "csv" { + t.Errorf("default format = %q", e.Format) + } + i := parseAs[*ast.ImportStatement](t, "IMPORT DATABASE 'dir'") + if i.Path != "dir" { + t.Errorf("import = %+v", i) + } +} + +func TestPreparedStatementShapes(t *testing.T) { + p := parseAs[*ast.PrepareStatement](t, "PREPARE q AS SELECT $1, $2") + if p.Name != "q" { + t.Errorf("prepare = %+v", p) + } + if _, ok := p.Statement.(*ast.SelectStatement); !ok { + t.Errorf("prepared statement = %T", p.Statement) + } + if msg := parseErr(t, "PREPARE q AS CREATE TABLE t (i INT)"); !strings.Contains(msg, "not a preparable statement") { + t.Errorf("non-preparable error = %q", msg) + } + ex := parseAs[*ast.ExecuteStatement](t, "EXECUTE q(1, x := 2)") + if ex.Name != "q" || len(ex.Values) != 2 { + t.Fatalf("execute = %+v", ex) + } + // positional arguments are named by position + if ex.Values[0].Name != "1" || ex.Values[1].Name != "x" { + t.Errorf("execute values = %+v", ex.Values) + } + d := parseAs[*ast.DeallocateStatement](t, "DEALLOCATE PREPARE q") + if d.Name != "q" { + t.Errorf("deallocate = %+v", d) + } +} + +func TestExplainShapes(t *testing.T) { + e := parseAs[*ast.ExplainStatement](t, "EXPLAIN SELECT 1") + if e.Type != ast.ExplainStandard { + t.Errorf("explain type = %q", e.Type) + } + if _, ok := e.Statement.(*ast.SelectStatement); !ok { + t.Errorf("explained statement = %T", e.Statement) + } + e = parseAs[*ast.ExplainStatement](t, "EXPLAIN ANALYZE UPDATE t SET a = 1") + if e.Type != ast.ExplainAnalyze { + t.Errorf("explain analyze type = %q", e.Type) + } + if _, ok := e.Statement.(*ast.UpdateStatement); !ok { + t.Errorf("explained statement = %T", e.Statement) + } + e = parseAs[*ast.ExplainStatement](t, "EXPLAIN (FORMAT json) SELECT 1") + if e.Format != "json" { + t.Errorf("format = %q", e.Format) + } +} + +func TestAttachDetachConnectShapes(t *testing.T) { + at := parseAs[*ast.AttachStatement](t, "ATTACH DATABASE 'file.db' AS db (READ_ONLY, TYPE sqlite)") + if at.Alias != "db" || at.OnConflict != ast.CreateError || len(at.Options) != 2 { + t.Fatalf("attach = %+v", at) + } + if c, ok := at.Path.(*ast.ConstantExpression); !ok || c.Value.Str != "file.db" { + t.Errorf("path = %#v", at.Path) + } + if at.Options[0].Name != "read_only" || len(at.Options[0].Values) != 0 { + t.Errorf("bare option = %+v", at.Options[0]) + } + if at.Options[1].Name != "type" || at.Options[1].Values[0].Str != "sqlite" { + t.Errorf("valued option = %+v", at.Options[1]) + } + at = parseAs[*ast.AttachStatement](t, "ATTACH IF NOT EXISTS ':memory:' AS mem") + if at.OnConflict != ast.CreateIgnore { + t.Errorf("if-not-exists = %q", at.OnConflict) + } + if msg := parseErr(t, "ATTACH OR REPLACE IF NOT EXISTS 'x' AS y"); !strings.Contains(msg, "Cannot specify both") { + t.Errorf("conflict error = %q", msg) + } + dt := parseAs[*ast.DetachStatement](t, "DETACH DATABASE IF EXISTS db") + if dt.Name != "db" || !dt.IfExists { + t.Errorf("detach = %+v", dt) + } + cn := parseAs[*ast.ConnectStatement](t, "CONNECT LOCAL") + if !cn.Local { + t.Errorf("connect local = %+v", cn) + } + cn = parseAs[*ast.ConnectStatement](t, "CONNECT 'remote' (TOKEN 'abc')") + if cn.Name != "remote" || !cn.NameIsString || len(cn.Options) != 1 { + t.Errorf("connect string = %+v", cn) + } + parseAs[*ast.DisconnectStatement](t, "DISCONNECT") +} + +func TestCommentOnShapes(t *testing.T) { + c := parseAs[*ast.CommentOnStatement](t, "COMMENT ON TABLE s.t IS 'hi'") + if c.OnType != ast.CommentOnTable || c.Schema != "s" || c.Name != "t" { + t.Errorf("comment = %+v", c) + } + if v, ok := c.Value.(*ast.ConstantExpression); !ok || v.Value.Str != "hi" { + t.Errorf("comment value = %#v", c.Value) + } + c = parseAs[*ast.CommentOnStatement](t, "COMMENT ON COLUMN t.col IS NULL") + if c.OnType != ast.CommentOnColumn || c.Name != "t" || c.Column != "col" { + t.Errorf("column comment = %+v", c) + } + if v, ok := c.Value.(*ast.ConstantExpression); !ok || !v.Value.IsNull { + t.Errorf("null comment = %#v", c.Value) + } + if msg := parseErr(t, "COMMENT ON COLUMN c IS 'x'"); !strings.Contains(msg, "Invalid column reference") { + t.Errorf("bare column error = %q", msg) + } +} + +func TestExtensionStatementShapes(t *testing.T) { + l := parseAs[*ast.LoadStatement](t, "LOAD httpfs") + if l.Kind != ast.LoadTypeLoad || l.Name != "httpfs" { + t.Errorf("load = %+v", l) + } + l = parseAs[*ast.LoadStatement](t, "LOAD 'ext.duckdb_extension' AS my_ext") + if l.Kind != ast.LoadTypeLoadAs || l.Alias != "my_ext" { + t.Errorf("load as = %+v", l) + } + l = parseAs[*ast.LoadStatement](t, "FORCE INSTALL spatial FROM core_nightly VERSION 'v1.2'") + if l.Kind != ast.LoadTypeForceInstall || l.Name != "spatial" || + l.Repository != "core_nightly" || !l.RepoIsAlias || l.Version != "v1.2" { + t.Errorf("install = %+v", l) + } + l = parseAs[*ast.LoadStatement](t, "INSTALL x FROM 'https://repo'") + if l.Kind != ast.LoadTypeInstall || l.RepoIsAlias || l.Repository != "https://repo" { + t.Errorf("install from url = %+v", l) + } + u := parseAs[*ast.UpdateExtensionsStatement](t, "UPDATE EXTENSIONS (a, b)") + if !reflect.DeepEqual(u.Extensions, []string{"a", "b"}) { + t.Errorf("update extensions = %+v", u) + } + er := parseAs[*ast.ExternalResourceStatement](t, "CREATE EXTERNAL RESOURCE 'gpu' AS g (device 1)") + if er.Operation != ast.ExternalResourceCreate || er.Type != "gpu" || er.Name != "g" || len(er.Options) != 1 { + t.Errorf("create external resource = %+v", er) + } + er = parseAs[*ast.ExternalResourceStatement](t, "SHOW ALL EXTERNAL RESOURCES") + if er.Operation != ast.ExternalResourceShow || !er.All { + t.Errorf("show external resources = %+v", er) + } +} + +func TestCopyShapes(t *testing.T) { + c := parseAs[*ast.CopyStatement](t, "COPY s.t (a, b) FROM 'data.csv.gz' (HEADER, DELIMITER '|')") + info := c.Info + if info.Schema != "s" || info.Table != "t" || !reflect.DeepEqual(info.Columns, []string{"a", "b"}) { + t.Errorf("copy target = %+v", info) + } + if !info.IsFrom || info.FilePath != "data.csv.gz" || info.Format != "csv" { + t.Errorf("copy source = %+v", info) + } + if len(info.Options) != 2 || info.Options[0].Name != "header" || info.Options[1].Name != "delimiter" { + t.Errorf("options = %+v", info.Options) + } + // PostgreSQL-style specialized options without parentheses + c = parseAs[*ast.CopyStatement](t, "COPY t TO stdout CSV HEADER") + if c.Info.IsFrom || c.Info.FilePath != "/dev/stdout" || !c.Info.FormatExplicit || c.Info.Format != "csv" { + t.Errorf("specialized copy = %+v", c.Info) + } + // FORMAT option overrides the extension + c = parseAs[*ast.CopyStatement](t, "COPY t TO 'out.bin' (FORMAT parquet)") + if c.Info.Format != "parquet" || !c.Info.FormatExplicit { + t.Errorf("format option = %+v", c.Info) + } + if msg := parseErr(t, "COPY t TO 'f' (HEADER, HEADER)"); !strings.Contains(msg, "duplicate option") { + t.Errorf("duplicate option error = %q", msg) + } + // COPY (SELECT ...) TO + c = parseAs[*ast.CopyStatement](t, "COPY (SELECT 1) TO 'out.parquet'") + if c.Info.Query == nil || c.Info.Table != "" { + t.Errorf("copy select = %+v", c.Info) + } + // COPY FROM DATABASE + cd := parseAs[*ast.CopyDatabaseStatement](t, "COPY FROM DATABASE a TO b (SCHEMA)") + if cd.FromDatabase != "a" || cd.ToDatabase != "b" || cd.CopyType != ast.CopyDatabaseSchema { + t.Errorf("copy database = %+v", cd) + } + // the flagless form desugars into PRAGMA copy_database + p := parseAs[*ast.PragmaStatement](t, "COPY FROM DATABASE a TO b") + if p.Name != "copy_database" || len(p.Parameters) != 2 { + t.Errorf("copy database pragma = %+v", p) + } +} + +func TestCopyInCTE(t *testing.T) { + sel := parseAs[*ast.SelectStatement](t, + "WITH c AS (COPY t TO 'out.csv') SELECT * FROM c") + ctes := sel.Node.CTEMapRef() + if len(ctes.Entries) != 1 { + t.Fatalf("cte entries = %d", len(ctes.Entries)) + } + if _, ok := ctes.Entries[0].CTE.Query.(*ast.CopyQueryNode); !ok { + t.Errorf("cte body = %T, want *ast.CopyQueryNode", ctes.Entries[0].CTE.Query) + } + if msg := parseErr(t, "WITH c AS (COPY t FROM 'in.csv') SELECT 1"); !strings.Contains(msg, "COPY FROM cannot") { + t.Errorf("copy-from CTE error = %q", msg) + } + if msg := parseErr(t, "WITH RECURSIVE c AS (COPY t TO 'o.csv') SELECT 1"); !strings.Contains(msg, "Recursive CTEs with COPY") { + t.Errorf("recursive copy CTE error = %q", msg) + } +} + +func TestCreateMacroShapes(t *testing.T) { + info := createInfo[*ast.CreateMacroInfo](t, "CREATE MACRO add2(a, b := 5) AS a + b") + if info.Name != "add2" || info.IsTable || len(info.Macros) != 1 { + t.Fatalf("macro = %+v", info) + } + m := info.Macros[0] + if len(m.Parameters) != 2 || m.Parameters[0].Name != "a" || m.Parameters[1].Name != "b" { + t.Fatalf("parameters = %+v", m.Parameters) + } + if m.Parameters[0].Default != nil || m.Parameters[1].Default == nil { + t.Errorf("defaults = %+v", m.Parameters) + } + if m.Expr == nil || m.Query != nil { + t.Errorf("body = %+v", m) + } + // scalar overloads + info = createInfo[*ast.CreateMacroInfo](t, "CREATE MACRO m1(a) AS a, (a, b) AS a + b") + if info.IsTable || len(info.Macros) != 2 { + t.Errorf("overloads = %+v", info) + } + // table macros + info = createInfo[*ast.CreateMacroInfo](t, "CREATE MACRO t1(x) AS TABLE SELECT x") + if !info.IsTable || len(info.Macros) != 1 || info.Macros[0].Query == nil { + t.Errorf("table macro = %+v", info) + } + if msg := parseErr(t, "CREATE MACRO m(a, a) AS a"); !strings.Contains(msg, "Duplicate parameter") { + t.Errorf("dup param error = %q", msg) + } + if msg := parseErr(t, "CREATE MACRO m(a := 1, b) AS a + b"); !strings.Contains(msg, "without a default") { + t.Errorf("default order error = %q", msg) + } + if msg := parseErr(t, "CREATE MACRO m() AS 1, () AS TABLE SELECT 1"); !strings.Contains(msg, "Cannot mix") { + t.Errorf("mixed macro error = %q", msg) + } +} + +func TestCreateSecretShapes(t *testing.T) { + info := createInfo[*ast.CreateSecretInfo](t, "CREATE SECRET (TYPE s3, KEY_ID 'k')") + if info.Name != "__default_s3" { + t.Errorf("default name = %q", info.Name) + } + if info.Type == nil || len(info.Options) != 1 || info.Options[0].Name != "key_id" { + t.Errorf("secret = %+v", info) + } + info = createInfo[*ast.CreateSecretInfo](t, "CREATE SECRET my_secret IN motherduck (TYPE s3, SCOPE 's3://b')") + if info.Name != "my_secret" || info.Storage != "motherduck" || info.Scope == nil { + t.Errorf("named secret = %+v", info) + } + if msg := parseErr(t, "CREATE SECRET (KEY_ID 'k')"); !strings.Contains(msg, "must have a type") { + t.Errorf("missing type error = %q", msg) + } +} + +func TestCreateTriggerShapes(t *testing.T) { + info := createInfo[*ast.CreateTriggerInfo](t, `CREATE TRIGGER trg AFTER UPDATE OF a, b ON s.t + REFERENCING NEW TABLE AS n OLD TABLE AS o + FOR EACH ROW + INSERT INTO log VALUES (1)`) + if info.Name != "trg" || info.Timing != ast.TriggerAfter || info.Event != ast.TriggerEventUpdate { + t.Errorf("trigger = %+v", info) + } + if !reflect.DeepEqual(info.Columns, []string{"a", "b"}) { + t.Errorf("columns = %v", info.Columns) + } + if info.Table == nil || info.Table.SchemaName != "s" || info.Table.TableName != "t" { + t.Errorf("table = %+v", info.Table) + } + if info.ReferencingNew != "n" || info.ReferencingOld != "o" || info.ForEach != ast.TriggerForEachRow { + t.Errorf("referencing = %+v", info) + } + if _, ok := info.Body.(*ast.InsertStatement); !ok { + t.Errorf("body = %T", info.Body) + } + if msg := parseErr(t, `CREATE TRIGGER trg AFTER INSERT ON t + REFERENCING NEW TABLE AS x NEW TABLE AS y + INSERT INTO l VALUES (1)`); !strings.Contains(msg, "cannot be specified multiple times") { + t.Errorf("dup referencing error = %q", msg) + } +} + +func TestPivotEntryChecks(t *testing.T) { + // data-extracted pivots cannot appear in views or macros + if msg := parseErr(t, "CREATE VIEW v AS PIVOT t ON x USING sum(y)"); !strings.Contains(msg, "cannot be used in views") { + t.Errorf("pivot view error = %q", msg) + } + // with an explicit IN list they can + parseOne(t, "CREATE VIEW v AS PIVOT t ON x IN (a, b) USING sum(y)") + // parameters cannot mix with data extraction + if msg := parseErr(t, "PIVOT (SELECT a + ? AS a FROM t) ON a USING sum(a)"); !strings.Contains(msg, "cannot have parameters") { + t.Errorf("pivot params error = %q", msg) + } +} + +func TestSequenceOptionChecks(t *testing.T) { + if msg := parseErr(t, "CREATE SEQUENCE s START 13 START WITH 3"); !strings.Contains(msg, "Start should be passed at most once") { + t.Errorf("dup start = %q", msg) + } + if msg := parseErr(t, "CREATE SEQUENCE s START NULL"); !strings.Contains(msg, "START value must not be NULL") { + t.Errorf("null start = %q", msg) + } + if msg := parseErr(t, "CREATE SEQUENCE s MINVALUE 7 MAXVALUE 5"); !strings.Contains(msg, "must be less than MAXVALUE") { + t.Errorf("min/max = %q", msg) + } + if msg := parseErr(t, "CREATE SEQUENCE s INCREMENT 0"); !strings.Contains(msg, "Increment must not be zero") { + t.Errorf("zero increment = %q", msg) + } + // a negative increment flips the defaults + if msg := parseErr(t, "CREATE SEQUENCE s INCREMENT -1 START 1 CYCLE"); !strings.Contains(msg, "cannot be greater than MAXVALUE (-1)") { + t.Errorf("negative increment default = %q", msg) + } + parseOne(t, "CREATE SEQUENCE s INCREMENT 2 MINVALUE 0 MAXVALUE 100 START 4 CYCLE") +} diff --git a/parser/parser.go b/parser/parser.go index 16a869d..0f15ba9 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -32,21 +32,11 @@ func (e *Error) Error() string { return "Parser Error: " + e.Msg } -// ErrUnsupported marks statements the transformer does not cover yet -// (milestone 5 territory: COPY, SET, PRAGMA, ...). The engine accepted -// the statement; only the AST is missing. +// ErrUnsupported marked statements the transformer did not cover yet. +// Since milestone 5 every statement has a transformer and Parse no longer +// returns it; the variable stays for API compatibility. var ErrUnsupported = errors.New("darkwing: statement not supported yet") -type unsupportedError struct { - rule string -} - -func (e *unsupportedError) Error() string { - return fmt.Sprintf("darkwing: statement not supported yet (%s)", e.rule) -} - -func (e *unsupportedError) Unwrap() error { return ErrUnsupported } - // Parse reads SQL from r and returns its statements. Statement spans tile // the input: statement i covers [prev end, own end), the first starts at // 0 and the last ends at len(input), so slicing the source by span @@ -133,6 +123,13 @@ func parseString(ctx context.Context, src string) (stmts []ast.Stmt, err error) if stmt == nil { continue } + // port of CreatePivotStatement's parameter check: data-extracted + // pivot values cannot mix with prepared parameters + if tc.pivotEntries > 0 && tc.pivotEntryHasParams { + raise("PIVOT statements with pivot elements extracted from the data cannot have parameters in their source.\n" + + "In order to use parameters the PIVOT values must be manually specified, e.g.:\n" + + "PIVOT ... ON ... IN (val1, val2, ...)") + } setStmtSpan(stmt, ast.Span{Start: prevEnd, End: end}) tc.finishStatement(stmt) stmts = append(stmts, stmt) @@ -185,8 +182,6 @@ func catchInternal(err *error) { *err = e case *Error: *err = e - case *internalErrorUnsupported: - *err = &unsupportedError{rule: e.rule} default: panic(r) } diff --git a/parser/parser_test.go b/parser/parser_test.go index 19a3aa1..ba36601 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -5,10 +5,11 @@ // cmd/next-test for picking the next todo case). // // Since milestone 3 the classification runs the full Parse pipeline, so -// transformer-raised rejects (chained comparisons, parameter mixing, ...) -// count alongside the engine's syntax errors. Statements the transformer -// does not cover yet (ErrUnsupported, milestone 5) are accepts: the -// engine parsed them. +// transformer-raised rejects (chained comparisons, recursive CTE +// restrictions, ...) count alongside the engine's syntax errors. Since +// milestone 5 every statement has a transformer; remaining todo entries +// cover oracle rejects raised outside the parse pipeline (bind-time +// validation, nested SQL parsing). package parser import ( @@ -33,8 +34,9 @@ var ( // verdict is darkwing's accept/reject classification of one statement, // mirroring the oracle's: tokenizer errors, matcher syntax errors and -// transformer-raised parser errors are rejects; anything else — including -// statements whose transformer is still missing — is an accept. +// transformer-raised parser errors are rejects; anything else is an +// accept (ErrUnsupported is kept in the accept path for compatibility, +// though Parse no longer returns it). type verdict struct { reject bool detail string diff --git a/parser/testdata/corpus/fuzzer/duckfuzz/null_arguments.test.metadata.json b/parser/testdata/corpus/fuzzer/duckfuzz/null_arguments.test.metadata.json index dfd9891..4259412 100644 --- a/parser/testdata/corpus/fuzzer/duckfuzz/null_arguments.test.metadata.json +++ b/parser/testdata/corpus/fuzzer/duckfuzz/null_arguments.test.metadata.json @@ -1,7 +1,7 @@ { "todo": { - "04d20d2d40f4c383": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "372c3f6998a25c4f": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "ae69580fe9b6f013": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "04d20d2d40f4c383": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "372c3f6998a25c4f": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "ae69580fe9b6f013": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/fuzzer/duckfuzz/regex_syntax_2889.test.metadata.json b/parser/testdata/corpus/fuzzer/duckfuzz/regex_syntax_2889.test.metadata.json deleted file mode 100644 index 3748dc6..0000000 --- a/parser/testdata/corpus/fuzzer/duckfuzz/regex_syntax_2889.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "6e4261695ad1aa36": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/fuzzer/pedro/foreign_key_binding_issue.test.metadata.json b/parser/testdata/corpus/fuzzer/pedro/foreign_key_binding_issue.test.metadata.json index 4552362..9170817 100644 --- a/parser/testdata/corpus/fuzzer/pedro/foreign_key_binding_issue.test.metadata.json +++ b/parser/testdata/corpus/fuzzer/pedro/foreign_key_binding_issue.test.metadata.json @@ -1,7 +1,7 @@ { "todo": { - "3dd4680747d25c85": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "f630e248a8b93d80": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "fff2fa19f530201f": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "3dd4680747d25c85": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "f630e248a8b93d80": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "fff2fa19f530201f": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/fuzzer/public/pragma_named_parameters.test.metadata.json b/parser/testdata/corpus/fuzzer/public/pragma_named_parameters.test.metadata.json deleted file mode 100644 index f40c09a..0000000 --- a/parser/testdata/corpus/fuzzer/public/pragma_named_parameters.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "7f4418d74ce6552f": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/aggregate/aggregate_state_export/test_state_export_opaque.test.metadata.json b/parser/testdata/corpus/sql/aggregate/aggregate_state_export/test_state_export_opaque.test.metadata.json deleted file mode 100644 index 4ce1044..0000000 --- a/parser/testdata/corpus/sql/aggregate/aggregate_state_export/test_state_export_opaque.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "6bcef3fc22f8617b": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/aggregate/aggregate_state_export/test_state_export_struct.test.metadata.json b/parser/testdata/corpus/sql/aggregate/aggregate_state_export/test_state_export_struct.test.metadata.json deleted file mode 100644 index 1c5bc0f..0000000 --- a/parser/testdata/corpus/sql/aggregate/aggregate_state_export/test_state_export_struct.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "98c1f858af0c93f2": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/aggregate/aggregates/test_ordered_aggregates.test.metadata.json b/parser/testdata/corpus/sql/aggregate/aggregates/test_ordered_aggregates.test.metadata.json deleted file mode 100644 index ad4a1dc..0000000 --- a/parser/testdata/corpus/sql/aggregate/aggregates/test_ordered_aggregates.test.metadata.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "todo": { - "1cd3dc66ee1babf4": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "587c503e21fcb4ef": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "6e6328bcf7c20489": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "7f59824aeae80576": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "9360b7f8a9a72f05": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "d20b324f2dfc9147": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "eeb448437cd388cc": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/aggregate/aggregates/test_sum.test.metadata.json b/parser/testdata/corpus/sql/aggregate/aggregates/test_sum.test.metadata.json deleted file mode 100644 index e96cb89..0000000 --- a/parser/testdata/corpus/sql/aggregate/aggregates/test_sum.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "55e359377905e882": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/aggregate/grouping_sets/cube.test.metadata.json b/parser/testdata/corpus/sql/aggregate/grouping_sets/cube.test.metadata.json deleted file mode 100644 index 4aa0228..0000000 --- a/parser/testdata/corpus/sql/aggregate/grouping_sets/cube.test.metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "todo": { - "67783085c99e617e": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "6ec4e705eca4140d": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "dccfdc31b78ecfda": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/aggregate/grouping_sets/rollup.test.metadata.json b/parser/testdata/corpus/sql/aggregate/grouping_sets/rollup.test.metadata.json deleted file mode 100644 index a994bb0..0000000 --- a/parser/testdata/corpus/sql/aggregate/grouping_sets/rollup.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "8dce076071091b7a": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/alter/add_col/test_add_col_incorrect.test.metadata.json b/parser/testdata/corpus/sql/alter/add_col/test_add_col_incorrect.test.metadata.json deleted file mode 100644 index 4e94fcf..0000000 --- a/parser/testdata/corpus/sql/alter/add_col/test_add_col_incorrect.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "12b6cadced0b435f": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/alter/alter_table_set_table_options.test.metadata.json b/parser/testdata/corpus/sql/alter/alter_table_set_table_options.test.metadata.json deleted file mode 100644 index cb7a9c1..0000000 --- a/parser/testdata/corpus/sql/alter/alter_table_set_table_options.test.metadata.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "todo": { - "2131189ef0747278": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "3a4a1eb80e178aac": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/alter/alter_type/test_alter_type.test.metadata.json b/parser/testdata/corpus/sql/alter/alter_type/test_alter_type.test.metadata.json deleted file mode 100644 index 80b996e..0000000 --- a/parser/testdata/corpus/sql/alter/alter_type/test_alter_type.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "635891b97530349f": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/attach/reattach_schema.test.metadata.json b/parser/testdata/corpus/sql/attach/reattach_schema.test.metadata.json deleted file mode 100644 index f82090c..0000000 --- a/parser/testdata/corpus/sql/attach/reattach_schema.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "b11ee77530666b1a": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/catalog/comment_on_extended.test.metadata.json b/parser/testdata/corpus/sql/catalog/comment_on_extended.test.metadata.json deleted file mode 100644 index c12ea5f..0000000 --- a/parser/testdata/corpus/sql/catalog/comment_on_extended.test.metadata.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "todo": { - "56b2cf63d0c96a20": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "f1aa3d9bed842ca3": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/catalog/dependencies/test_alter_dependency_ownership.test.metadata.json b/parser/testdata/corpus/sql/catalog/dependencies/test_alter_dependency_ownership.test.metadata.json deleted file mode 100644 index 7639bbc..0000000 --- a/parser/testdata/corpus/sql/catalog/dependencies/test_alter_dependency_ownership.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "65fba09c620dd863": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/catalog/function/query_function.test.metadata.json b/parser/testdata/corpus/sql/catalog/function/query_function.test.metadata.json index f82f6c0..83bd821 100644 --- a/parser/testdata/corpus/sql/catalog/function/query_function.test.metadata.json +++ b/parser/testdata/corpus/sql/catalog/function/query_function.test.metadata.json @@ -1,14 +1,14 @@ { - "todo": { - "2dbe98fbfd26e976": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "6849cf25332caa00": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "aa55a1e51d55e50f": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "b1a7071252780b40": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "c0ff2e8320e39445": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "c5d546a35986dc04": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - }, "skip": { "5f81be9e9bc42318": "nested parse at bind time: the inner SQL string is parsed by a function, the grammar never sees it", "f84558899aa09cea": "nested parse at bind time: the inner SQL string is parsed by a function, the grammar never sees it" + }, + "todo": { + "2dbe98fbfd26e976": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "6849cf25332caa00": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "aa55a1e51d55e50f": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "b1a7071252780b40": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "c0ff2e8320e39445": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "c5d546a35986dc04": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/catalog/function/test_simple_macro.test.metadata.json b/parser/testdata/corpus/sql/catalog/function/test_simple_macro.test.metadata.json deleted file mode 100644 index 277e1ef..0000000 --- a/parser/testdata/corpus/sql/catalog/function/test_simple_macro.test.metadata.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "todo": { - "5c08f89028c1de02": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "7dca7ca4eab6a6fb": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "8c107818c0249ab5": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "b6d9d3d217115c2b": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "da128795699d6786": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/catalog/function/test_table_macro_args.test.metadata.json b/parser/testdata/corpus/sql/catalog/function/test_table_macro_args.test.metadata.json deleted file mode 100644 index bae2b14..0000000 --- a/parser/testdata/corpus/sql/catalog/function/test_table_macro_args.test.metadata.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "todo": { - "9f3aa745137aa39f": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "bee16037c4cea027": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/catalog/nested_schema/nested_entries.test.metadata.json b/parser/testdata/corpus/sql/catalog/nested_schema/nested_entries.test.metadata.json index 63a1f5f..c329fb4 100644 --- a/parser/testdata/corpus/sql/catalog/nested_schema/nested_entries.test.metadata.json +++ b/parser/testdata/corpus/sql/catalog/nested_schema/nested_entries.test.metadata.json @@ -1,5 +1,5 @@ { "todo": { - "c2d50285cb7509bb": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "c2d50285cb7509bb": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/catalog/sequence/test_sequence.test.metadata.json b/parser/testdata/corpus/sql/catalog/sequence/test_sequence.test.metadata.json index b4f63a0..04032af 100644 --- a/parser/testdata/corpus/sql/catalog/sequence/test_sequence.test.metadata.json +++ b/parser/testdata/corpus/sql/catalog/sequence/test_sequence.test.metadata.json @@ -1,23 +1,6 @@ { "todo": { - "132d63d61772c3bf": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "17ad559be3bc0bed": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "2e1581541df8f2d4": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "31e44341fd615e6e": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "4824d3a8f34f1904": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "49e80f12d8463a99": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "4a727b414ee9e3aa": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "4b6b034abfeec7e8": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "5966969ec1140fad": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "681dfde4704609ce": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "6f8570edee9b3d74": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "75fba905bb243877": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "809350ad9fc71a21": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "8f2945249abe5158": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "9646d43ed9a210ab": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "b5c2331745db52df": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "cf44ff528cab7ec3": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "dfda3a0ae8a0c287": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "e4841867b271b34c": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "49e80f12d8463a99": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "9646d43ed9a210ab": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/catalog/table/test_default_values.test.metadata.json b/parser/testdata/corpus/sql/catalog/table/test_default_values.test.metadata.json deleted file mode 100644 index f014efc..0000000 --- a/parser/testdata/corpus/sql/catalog/table/test_default_values.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "ceb1e6f9f97931ac": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/catalog/test_incorrect_table_creation.test.metadata.json b/parser/testdata/corpus/sql/catalog/test_incorrect_table_creation.test.metadata.json index c584a86..c0cee23 100644 --- a/parser/testdata/corpus/sql/catalog/test_incorrect_table_creation.test.metadata.json +++ b/parser/testdata/corpus/sql/catalog/test_incorrect_table_creation.test.metadata.json @@ -1,7 +1,7 @@ { "todo": { - "24a5d9123261c84d": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "91b15dc29617d59c": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "b542c6a3df03ddb8": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "24a5d9123261c84d": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "91b15dc29617d59c": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "b542c6a3df03ddb8": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/catalog/test_set_search_path.test.metadata.json b/parser/testdata/corpus/sql/catalog/test_set_search_path.test.metadata.json index 0a73c16..f307b49 100644 --- a/parser/testdata/corpus/sql/catalog/test_set_search_path.test.metadata.json +++ b/parser/testdata/corpus/sql/catalog/test_set_search_path.test.metadata.json @@ -1,6 +1,6 @@ { "todo": { - "64443d42a674a5dd": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "c08e8748bc9dce3d": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "64443d42a674a5dd": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "c08e8748bc9dce3d": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/catalog/test_temporary.test.metadata.json b/parser/testdata/corpus/sql/catalog/test_temporary.test.metadata.json index 8523322..b40925c 100644 --- a/parser/testdata/corpus/sql/catalog/test_temporary.test.metadata.json +++ b/parser/testdata/corpus/sql/catalog/test_temporary.test.metadata.json @@ -1,5 +1,5 @@ { "todo": { - "af16bac22724a9c9": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "af16bac22724a9c9": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/collate/test_unsupported_collations.test.metadata.json b/parser/testdata/corpus/sql/collate/test_unsupported_collations.test.metadata.json index a97fb23..a855d7a 100644 --- a/parser/testdata/corpus/sql/collate/test_unsupported_collations.test.metadata.json +++ b/parser/testdata/corpus/sql/collate/test_unsupported_collations.test.metadata.json @@ -1,5 +1,5 @@ { "todo": { - "eb15b92628017c19": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "eb15b92628017c19": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/constraints/check/test_check.test.metadata.json b/parser/testdata/corpus/sql/constraints/check/test_check.test.metadata.json deleted file mode 100644 index 554998d..0000000 --- a/parser/testdata/corpus/sql/constraints/check/test_check.test.metadata.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "todo": { - "62968f60bd8a6ea6": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "a81fe167c4d02b92": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/constraints/foreignkey/test_action.test.metadata.json b/parser/testdata/corpus/sql/constraints/foreignkey/test_action.test.metadata.json deleted file mode 100644 index b1c0fcb..0000000 --- a/parser/testdata/corpus/sql/constraints/foreignkey/test_action.test.metadata.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "todo": { - "5a5620b9f1876141": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "798f960dc772a64a": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "81ee07e70de6c75c": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "b5391f27b3d830bf": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "c48915e973daf228": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "cccd1b3b0fc27fa4": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/constraints/foreignkey/test_foreignkey.test.metadata.json b/parser/testdata/corpus/sql/constraints/foreignkey/test_foreignkey.test.metadata.json deleted file mode 100644 index fc2be33..0000000 --- a/parser/testdata/corpus/sql/constraints/foreignkey/test_foreignkey.test.metadata.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "todo": { - "97428731c1de2c0b": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "a79b6e6f6b9fc911": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/copy/copy_cte.test.metadata.json b/parser/testdata/corpus/sql/copy/copy_cte.test.metadata.json deleted file mode 100644 index 8181349..0000000 --- a/parser/testdata/corpus/sql/copy/copy_cte.test.metadata.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "todo": { - "33bee8e3be0a0dbb": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "994308b4a109b147": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/copy/csv/csv_duck_fuzz.test.metadata.json b/parser/testdata/corpus/sql/copy/csv/csv_duck_fuzz.test.metadata.json index 4158379..76ec51f 100644 --- a/parser/testdata/corpus/sql/copy/csv/csv_duck_fuzz.test.metadata.json +++ b/parser/testdata/corpus/sql/copy/csv/csv_duck_fuzz.test.metadata.json @@ -1,5 +1,5 @@ { "todo": { - "3eede6463cafd305": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "3eede6463cafd305": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/copy/csv/glob/read_csv_glob.test.metadata.json b/parser/testdata/corpus/sql/copy/csv/glob/read_csv_glob.test.metadata.json index bee18ab..4ce724f 100644 --- a/parser/testdata/corpus/sql/copy/csv/glob/read_csv_glob.test.metadata.json +++ b/parser/testdata/corpus/sql/copy/csv/glob/read_csv_glob.test.metadata.json @@ -1,8 +1,8 @@ { "todo": { - "0f661413e61de6d5": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "1744b353dc7a29b0": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "715542898e8196a6": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "7416adf675a44fe4": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "0f661413e61de6d5": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "1744b353dc7a29b0": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "715542898e8196a6": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "7416adf675a44fe4": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/copy/csv/read_csv_variable.test.metadata.json b/parser/testdata/corpus/sql/copy/csv/read_csv_variable.test.metadata.json index 21cd7bc..2a27f82 100644 --- a/parser/testdata/corpus/sql/copy/csv/read_csv_variable.test.metadata.json +++ b/parser/testdata/corpus/sql/copy/csv/read_csv_variable.test.metadata.json @@ -1,5 +1,5 @@ { "todo": { - "d7806464bf25c9a5": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "d7806464bf25c9a5": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/copy/csv/test_copy.test.metadata.json b/parser/testdata/corpus/sql/copy/csv/test_copy.test.metadata.json deleted file mode 100644 index 6c59e24..0000000 --- a/parser/testdata/corpus/sql/copy/csv/test_copy.test.metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "todo": { - "a021fe27e1ab7b97": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "b08fcfb601074519": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "c677abb2b9f03ec4": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/copy/parquet/parquet_list.test.metadata.json b/parser/testdata/corpus/sql/copy/parquet/parquet_list.test.metadata.json index 49fc83f..1d873ff 100644 --- a/parser/testdata/corpus/sql/copy/parquet/parquet_list.test.metadata.json +++ b/parser/testdata/corpus/sql/copy/parquet/parquet_list.test.metadata.json @@ -1,7 +1,7 @@ { "todo": { - "3f979f656adf7bf3": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "b1f1a96914a59f83": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "f36a81f7dcade3eb": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "3f979f656adf7bf3": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "b1f1a96914a59f83": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "f36a81f7dcade3eb": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/copy/parquet/parquet_virtual_file_row_group_number.test.metadata.json b/parser/testdata/corpus/sql/copy/parquet/parquet_virtual_file_row_group_number.test.metadata.json index d5e4337..cd80189 100644 --- a/parser/testdata/corpus/sql/copy/parquet/parquet_virtual_file_row_group_number.test.metadata.json +++ b/parser/testdata/corpus/sql/copy/parquet/parquet_virtual_file_row_group_number.test.metadata.json @@ -1,5 +1,5 @@ { "todo": { - "a2b6a2683cf01059": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "a2b6a2683cf01059": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/copy/parquet/writer/parquet_write_memory_usage.test.metadata.json b/parser/testdata/corpus/sql/copy/parquet/writer/parquet_write_memory_usage.test.metadata.json index 4308a63..67a837a 100644 --- a/parser/testdata/corpus/sql/copy/parquet/writer/parquet_write_memory_usage.test.metadata.json +++ b/parser/testdata/corpus/sql/copy/parquet/writer/parquet_write_memory_usage.test.metadata.json @@ -1,5 +1,5 @@ { "todo": { - "ff0a57b31734b7cb": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "ff0a57b31734b7cb": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/copy/partitioned/partition_by_syntax.test.metadata.json b/parser/testdata/corpus/sql/copy/partitioned/partition_by_syntax.test.metadata.json deleted file mode 100644 index 3506cf7..0000000 --- a/parser/testdata/corpus/sql/copy/partitioned/partition_by_syntax.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "5ff20aa75bc2bb59": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/create/create_table_as_error.test.metadata.json b/parser/testdata/corpus/sql/create/create_table_as_error.test.metadata.json deleted file mode 100644 index c4c860e..0000000 --- a/parser/testdata/corpus/sql/create/create_table_as_error.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "d611cbbbda4a5820": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/create/create_table_empty_name.test.metadata.json b/parser/testdata/corpus/sql/create/create_table_empty_name.test.metadata.json index e58eca0..0f5f060 100644 --- a/parser/testdata/corpus/sql/create/create_table_empty_name.test.metadata.json +++ b/parser/testdata/corpus/sql/create/create_table_empty_name.test.metadata.json @@ -1,5 +1,5 @@ { "todo": { - "6d38638491130c28": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "6d38638491130c28": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/cte/cte_describe.test.metadata.json b/parser/testdata/corpus/sql/cte/cte_describe.test.metadata.json index 4866695..8951f9a 100644 --- a/parser/testdata/corpus/sql/cte/cte_describe.test.metadata.json +++ b/parser/testdata/corpus/sql/cte/cte_describe.test.metadata.json @@ -1,6 +1,6 @@ { "todo": { - "03100e0115b4a66b": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "e993403701548ead": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "03100e0115b4a66b": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "e993403701548ead": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/cte/dml_cte.test.metadata.json b/parser/testdata/corpus/sql/cte/dml_cte.test.metadata.json deleted file mode 100644 index 7b42284..0000000 --- a/parser/testdata/corpus/sql/cte/dml_cte.test.metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "todo": { - "6f9e83f76d8baaf5": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "7da2a7c8da13c934": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "7eed85fe16a65e3a": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/cte/materialized/test_cte_in_cte_materialized.test.metadata.json b/parser/testdata/corpus/sql/cte/materialized/test_cte_in_cte_materialized.test.metadata.json deleted file mode 100644 index 42eca95..0000000 --- a/parser/testdata/corpus/sql/cte/materialized/test_cte_in_cte_materialized.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "2ff0e65fff596023": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/cte/materialized/test_recursive_cte_union_all_materialized.test.metadata.json b/parser/testdata/corpus/sql/cte/materialized/test_recursive_cte_union_all_materialized.test.metadata.json deleted file mode 100644 index 28bd9e0..0000000 --- a/parser/testdata/corpus/sql/cte/materialized/test_recursive_cte_union_all_materialized.test.metadata.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "todo": { - "5cadd1690fbb969b": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "a2a5280d2fbd16ba": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "b3331f3032bac086": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "b98b306ae5af8006": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/cte/materialized/test_recursive_cte_union_materialized.test.metadata.json b/parser/testdata/corpus/sql/cte/materialized/test_recursive_cte_union_materialized.test.metadata.json deleted file mode 100644 index 13c3460..0000000 --- a/parser/testdata/corpus/sql/cte/materialized/test_recursive_cte_union_materialized.test.metadata.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "todo": { - "08a4f1886d350a18": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "3c0f6a66b5b3f8dd": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "501d01b30d4755ab": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "818a825b6c349c9a": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/cte/test_cte.test.metadata.json b/parser/testdata/corpus/sql/cte/test_cte.test.metadata.json deleted file mode 100644 index 91f227f..0000000 --- a/parser/testdata/corpus/sql/cte/test_cte.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "2184b745d9f86eaa": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/cte/test_recursive_cte_union.test.metadata.json b/parser/testdata/corpus/sql/cte/test_recursive_cte_union.test.metadata.json deleted file mode 100644 index 3817b94..0000000 --- a/parser/testdata/corpus/sql/cte/test_recursive_cte_union.test.metadata.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "todo": { - "58ed9aa2c0f4d4eb": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "65625a060134bdc4": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "87bd2077e6146fce": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "c9d8ae7b511982b7": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/cte/test_recursive_cte_union_all.test.metadata.json b/parser/testdata/corpus/sql/cte/test_recursive_cte_union_all.test.metadata.json deleted file mode 100644 index 296d847..0000000 --- a/parser/testdata/corpus/sql/cte/test_recursive_cte_union_all.test.metadata.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "todo": { - "1f6365b8300078fe": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "2921959446915b6e": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "2d17eaa5e84d5941": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "a9c8ac4a5bcf2b6c": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/explain/test_explain_analyze.test.metadata.json b/parser/testdata/corpus/sql/explain/test_explain_analyze.test.metadata.json index f3fac1d..5afe9d2 100644 --- a/parser/testdata/corpus/sql/explain/test_explain_analyze.test.metadata.json +++ b/parser/testdata/corpus/sql/explain/test_explain_analyze.test.metadata.json @@ -1,6 +1,6 @@ { "todo": { - "193c1f87938969e8": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "ee40e452918b70e4": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "193c1f87938969e8": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "ee40e452918b70e4": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/export/export_database.test.metadata.json b/parser/testdata/corpus/sql/export/export_database.test.metadata.json deleted file mode 100644 index d6a82d6..0000000 --- a/parser/testdata/corpus/sql/export/export_database.test.metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "todo": { - "ae490c2eef80be6c": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "bb2ff7e267ffe77d": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "dbb1889bcce104a4": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/function/generic/test_set.test.metadata.json b/parser/testdata/corpus/sql/function/generic/test_set.test.metadata.json index 2ddb5cf..08dd3b8 100644 --- a/parser/testdata/corpus/sql/function/generic/test_set.test.metadata.json +++ b/parser/testdata/corpus/sql/function/generic/test_set.test.metadata.json @@ -1,5 +1,5 @@ { "todo": { - "ce1dfea629d91c5a": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "ce1dfea629d91c5a": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/function/generic/test_sleep.test.metadata.json b/parser/testdata/corpus/sql/function/generic/test_sleep.test.metadata.json index dc7d9c6..cc19f45 100644 --- a/parser/testdata/corpus/sql/function/generic/test_sleep.test.metadata.json +++ b/parser/testdata/corpus/sql/function/generic/test_sleep.test.metadata.json @@ -1,5 +1,5 @@ { "todo": { - "441cdc2cfa306834": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "441cdc2cfa306834": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/function/operator/test_comparison.test.metadata.json b/parser/testdata/corpus/sql/function/operator/test_comparison.test.metadata.json index bb6897a..148b01c 100644 --- a/parser/testdata/corpus/sql/function/operator/test_comparison.test.metadata.json +++ b/parser/testdata/corpus/sql/function/operator/test_comparison.test.metadata.json @@ -1,5 +1,5 @@ { "todo": { - "94bbc645841ee4b3": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "94bbc645841ee4b3": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/function/string/test_string_slice.test.metadata.json b/parser/testdata/corpus/sql/function/string/test_string_slice.test.metadata.json deleted file mode 100644 index 560d160..0000000 --- a/parser/testdata/corpus/sql/function/string/test_string_slice.test.metadata.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "todo": { - "3cbbda9f20c62757": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "42e2f1e99e063d83": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "b0bf995facf139bf": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "e24ce8a19cf28f7e": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/function/string/test_subscript.test.metadata.json b/parser/testdata/corpus/sql/function/string/test_subscript.test.metadata.json deleted file mode 100644 index 4cf640f..0000000 --- a/parser/testdata/corpus/sql/function/string/test_subscript.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "2b291c35a430c7b3": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/generated_columns/virtual/alter_table.test.metadata.json b/parser/testdata/corpus/sql/generated_columns/virtual/alter_table.test.metadata.json deleted file mode 100644 index 3efe944..0000000 --- a/parser/testdata/corpus/sql/generated_columns/virtual/alter_table.test.metadata.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "todo": { - "06ad91019f245b72": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "1e580bf960d161c0": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "b47bf0ffa43d4332": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "edb5239250179eb9": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/generated_columns/virtual/ambiguity.test.metadata.json b/parser/testdata/corpus/sql/generated_columns/virtual/ambiguity.test.metadata.json deleted file mode 100644 index 6f425ac..0000000 --- a/parser/testdata/corpus/sql/generated_columns/virtual/ambiguity.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "558a0624636dfa71": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/generated_columns/virtual/collate.test.metadata.json b/parser/testdata/corpus/sql/generated_columns/virtual/collate.test.metadata.json deleted file mode 100644 index a48b27b..0000000 --- a/parser/testdata/corpus/sql/generated_columns/virtual/collate.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "d3544f1572e0c863": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/generated_columns/virtual/create_table.test.metadata.json b/parser/testdata/corpus/sql/generated_columns/virtual/create_table.test.metadata.json deleted file mode 100644 index 1fdb161..0000000 --- a/parser/testdata/corpus/sql/generated_columns/virtual/create_table.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "7492b9a8ac417db0": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/generated_columns/virtual/default.test.metadata.json b/parser/testdata/corpus/sql/generated_columns/virtual/default.test.metadata.json deleted file mode 100644 index 5384371..0000000 --- a/parser/testdata/corpus/sql/generated_columns/virtual/default.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "96641e80d10d5022": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/generated_columns/virtual/from_generated_column.test.metadata.json b/parser/testdata/corpus/sql/generated_columns/virtual/from_generated_column.test.metadata.json deleted file mode 100644 index be5b796..0000000 --- a/parser/testdata/corpus/sql/generated_columns/virtual/from_generated_column.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "fbde2884feb212aa": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/generated_columns/virtual/implicit_type.test.metadata.json b/parser/testdata/corpus/sql/generated_columns/virtual/implicit_type.test.metadata.json deleted file mode 100644 index 0ee49ef..0000000 --- a/parser/testdata/corpus/sql/generated_columns/virtual/implicit_type.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "0eda6c1ffe5252f7": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/generated_columns/virtual/rowid.test.metadata.json b/parser/testdata/corpus/sql/generated_columns/virtual/rowid.test.metadata.json deleted file mode 100644 index 94c6da2..0000000 --- a/parser/testdata/corpus/sql/generated_columns/virtual/rowid.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "4a155647396b2908": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/insert/test_insert_invalid.test.metadata.json b/parser/testdata/corpus/sql/insert/test_insert_invalid.test.metadata.json deleted file mode 100644 index e022523..0000000 --- a/parser/testdata/corpus/sql/insert/test_insert_invalid.test.metadata.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "todo": { - "0c668fe32f2ac46c": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "cdd5ec719e153c6e": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/join/nearest/nearest_errors.test.metadata.json b/parser/testdata/corpus/sql/join/nearest/nearest_errors.test.metadata.json index 6994aa3..de0d2fb 100644 --- a/parser/testdata/corpus/sql/join/nearest/nearest_errors.test.metadata.json +++ b/parser/testdata/corpus/sql/join/nearest/nearest_errors.test.metadata.json @@ -1,9 +1,5 @@ { "todo": { - "07e88975ac7f20a0": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "27d1471e2bc2db2c": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "64c6e59cc6f48b32": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "c0c3d3d824ff120d": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "f89b2ba88a720463": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "07e88975ac7f20a0": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/join/test_join_by.test.metadata.json b/parser/testdata/corpus/sql/join/test_join_by.test.metadata.json index 5326472..e855a19 100644 --- a/parser/testdata/corpus/sql/join/test_join_by.test.metadata.json +++ b/parser/testdata/corpus/sql/join/test_join_by.test.metadata.json @@ -1,5 +1,5 @@ { "todo": { - "08dbeb99d6085f3a": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "08dbeb99d6085f3a": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/json/issues/issue22764.test.metadata.json b/parser/testdata/corpus/sql/json/issues/issue22764.test.metadata.json index 2150006..3d0ca24 100644 --- a/parser/testdata/corpus/sql/json/issues/issue22764.test.metadata.json +++ b/parser/testdata/corpus/sql/json/issues/issue22764.test.metadata.json @@ -1,5 +1,5 @@ { "todo": { - "c23202e0f507dfbc": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "c23202e0f507dfbc": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/json/test_json_serialize_sql.test.metadata.json b/parser/testdata/corpus/sql/json/test_json_serialize_sql.test.metadata.json index bd56e72..98a13c4 100644 --- a/parser/testdata/corpus/sql/json/test_json_serialize_sql.test.metadata.json +++ b/parser/testdata/corpus/sql/json/test_json_serialize_sql.test.metadata.json @@ -1,10 +1,10 @@ { - "todo": { - "0beceefbc70a3a8d": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "ebbdaa4a4b55e8d2": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - }, "skip": { "7b368bad1df6ae67": "nested parse at bind time: the inner SQL string is parsed by a function, the grammar never sees it", "c8a31233b8e3f7f6": "nested parse at bind time: the inner SQL string is parsed by a function, the grammar never sees it" + }, + "todo": { + "0beceefbc70a3a8d": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "ebbdaa4a4b55e8d2": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/merge/merge_into.test.metadata.json b/parser/testdata/corpus/sql/merge/merge_into.test.metadata.json deleted file mode 100644 index bc45ada..0000000 --- a/parser/testdata/corpus/sql/merge/merge_into.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "453f7c76e1e881bf": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/optimizer/nested_null_segment_pruning.test.metadata.json b/parser/testdata/corpus/sql/optimizer/nested_null_segment_pruning.test.metadata.json index 9fe17fa..253a886 100644 --- a/parser/testdata/corpus/sql/optimizer/nested_null_segment_pruning.test.metadata.json +++ b/parser/testdata/corpus/sql/optimizer/nested_null_segment_pruning.test.metadata.json @@ -1,5 +1,5 @@ { "todo": { - "7830ebec5e2d8f76": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "7830ebec5e2d8f76": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/optimizer/rewrite_nan_null.test.metadata.json b/parser/testdata/corpus/sql/optimizer/rewrite_nan_null.test.metadata.json index e398711..e962faa 100644 --- a/parser/testdata/corpus/sql/optimizer/rewrite_nan_null.test.metadata.json +++ b/parser/testdata/corpus/sql/optimizer/rewrite_nan_null.test.metadata.json @@ -1,5 +1,5 @@ { "todo": { - "d54780da5a3d98ea": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "d54780da5a3d98ea": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/optimizer/struct_segment_pruning.test.metadata.json b/parser/testdata/corpus/sql/optimizer/struct_segment_pruning.test.metadata.json index a40228a..cc5fc51 100644 --- a/parser/testdata/corpus/sql/optimizer/struct_segment_pruning.test.metadata.json +++ b/parser/testdata/corpus/sql/optimizer/struct_segment_pruning.test.metadata.json @@ -1,5 +1,5 @@ { "todo": { - "921bce1208fece2d": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "921bce1208fece2d": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/order/test_nulls_first.test.metadata.json b/parser/testdata/corpus/sql/order/test_nulls_first.test.metadata.json index 53fa86e..6cfcc6b 100644 --- a/parser/testdata/corpus/sql/order/test_nulls_first.test.metadata.json +++ b/parser/testdata/corpus/sql/order/test_nulls_first.test.metadata.json @@ -1,5 +1,5 @@ { "todo": { - "5cb1f0cf73c77783": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "5cb1f0cf73c77783": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/overflow/expression_tree_depth.test.metadata.json b/parser/testdata/corpus/sql/overflow/expression_tree_depth.test.metadata.json index 5b2a030..1528a5c 100644 --- a/parser/testdata/corpus/sql/overflow/expression_tree_depth.test.metadata.json +++ b/parser/testdata/corpus/sql/overflow/expression_tree_depth.test.metadata.json @@ -1,5 +1,5 @@ { "todo": { - "700a2a3f3ebdd6f9": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "700a2a3f3ebdd6f9": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/parser/values_list_error.test.metadata.json b/parser/testdata/corpus/sql/parser/values_list_error.test.metadata.json deleted file mode 100644 index af09779..0000000 --- a/parser/testdata/corpus/sql/parser/values_list_error.test.metadata.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "todo": { - "157130d4fe4932fa": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "ee5cf0bf5f0b1b24": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/peg_parser/escape_string.test.metadata.json b/parser/testdata/corpus/sql/peg_parser/escape_string.test.metadata.json deleted file mode 100644 index c801d30..0000000 --- a/parser/testdata/corpus/sql/peg_parser/escape_string.test.metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "todo": { - "1c8886b392c0c04a": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "7cfabb58230d67f1": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "e843a7b940b10b01": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/peg_parser/fuzzer/diverge_0020.test.metadata.json b/parser/testdata/corpus/sql/peg_parser/fuzzer/diverge_0020.test.metadata.json deleted file mode 100644 index 71c6de9..0000000 --- a/parser/testdata/corpus/sql/peg_parser/fuzzer/diverge_0020.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "c860d45d17d642d6": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/peg_parser/fuzzer/internal_0001.test.metadata.json b/parser/testdata/corpus/sql/peg_parser/fuzzer/internal_0001.test.metadata.json deleted file mode 100644 index 61ad85a..0000000 --- a/parser/testdata/corpus/sql/peg_parser/fuzzer/internal_0001.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "865fff04e40c569e": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/peg_parser/fuzzer/internal_0227.test.metadata.json b/parser/testdata/corpus/sql/peg_parser/fuzzer/internal_0227.test.metadata.json deleted file mode 100644 index 533a027..0000000 --- a/parser/testdata/corpus/sql/peg_parser/fuzzer/internal_0227.test.metadata.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "todo": { - "308c39e6a7c3410e": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "ce8bdf52f82ed226": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/peg_parser/peg_syntax_error.test.metadata.json b/parser/testdata/corpus/sql/peg_parser/peg_syntax_error.test.metadata.json deleted file mode 100644 index ef3795f..0000000 --- a/parser/testdata/corpus/sql/peg_parser/peg_syntax_error.test.metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "todo": { - "0e47440052e6de8f": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "b81f308accbd170a": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "f2d5692029146f8a": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/peg_parser/transformer/set_statement.test.metadata.json b/parser/testdata/corpus/sql/peg_parser/transformer/set_statement.test.metadata.json index 858be78..9811c4f 100644 --- a/parser/testdata/corpus/sql/peg_parser/transformer/set_statement.test.metadata.json +++ b/parser/testdata/corpus/sql/peg_parser/transformer/set_statement.test.metadata.json @@ -1,5 +1,5 @@ { "todo": { - "137ba824c42917c0": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "137ba824c42917c0": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/peg_parser/transformer/use_statement_trampoline.test.metadata.json b/parser/testdata/corpus/sql/peg_parser/transformer/use_statement_trampoline.test.metadata.json deleted file mode 100644 index db8de33..0000000 --- a/parser/testdata/corpus/sql/peg_parser/transformer/use_statement_trampoline.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "c826a808796eb361": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/pivot/pivot_expressions.test.metadata.json b/parser/testdata/corpus/sql/pivot/pivot_expressions.test.metadata.json deleted file mode 100644 index 1baae07..0000000 --- a/parser/testdata/corpus/sql/pivot/pivot_expressions.test.metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "todo": { - "114998ed400809b2": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "85d4bdf17dda06a9": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "feaf1903116b5ee0": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/pivot/pivot_prepare.test.metadata.json b/parser/testdata/corpus/sql/pivot/pivot_prepare.test.metadata.json deleted file mode 100644 index cbf25fb..0000000 --- a/parser/testdata/corpus/sql/pivot/pivot_prepare.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "e88aadafc6ebda1f": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/pivot/pivot_subquery.test.metadata.json b/parser/testdata/corpus/sql/pivot/pivot_subquery.test.metadata.json deleted file mode 100644 index 66794ff..0000000 --- a/parser/testdata/corpus/sql/pivot/pivot_subquery.test.metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "todo": { - "72437cd5e2d32c00": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "858d0b1660da9ca5": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "a3e1b09632521981": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/pivot/test_unpivot.test.metadata.json b/parser/testdata/corpus/sql/pivot/test_unpivot.test.metadata.json deleted file mode 100644 index b878b44..0000000 --- a/parser/testdata/corpus/sql/pivot/test_unpivot.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "d7ec37659c5b2fbc": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/pivot/top_level_pivot_syntax.test.metadata.json b/parser/testdata/corpus/sql/pivot/top_level_pivot_syntax.test.metadata.json deleted file mode 100644 index 62ad8d3..0000000 --- a/parser/testdata/corpus/sql/pivot/top_level_pivot_syntax.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "e5d746e954944af5": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/pragma/profiling/call_enable_profiling_function.test.metadata.json b/parser/testdata/corpus/sql/pragma/profiling/call_enable_profiling_function.test.metadata.json index a119d36..d25e2c8 100644 --- a/parser/testdata/corpus/sql/pragma/profiling/call_enable_profiling_function.test.metadata.json +++ b/parser/testdata/corpus/sql/pragma/profiling/call_enable_profiling_function.test.metadata.json @@ -1,7 +1,7 @@ { "todo": { - "3e3820d623be76c1": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "5f0ea5a77db63984": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "bbb93f23d864aa04": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "3e3820d623be76c1": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "5f0ea5a77db63984": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "bbb93f23d864aa04": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/pragma/profiling/test_attach_and_checkpoint_latency.test.metadata.json b/parser/testdata/corpus/sql/pragma/profiling/test_attach_and_checkpoint_latency.test.metadata.json index 418291b..fdfae67 100644 --- a/parser/testdata/corpus/sql/pragma/profiling/test_attach_and_checkpoint_latency.test.metadata.json +++ b/parser/testdata/corpus/sql/pragma/profiling/test_attach_and_checkpoint_latency.test.metadata.json @@ -1,5 +1,5 @@ { "todo": { - "22caeac19def70af": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "22caeac19def70af": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/pragma/profiling/test_autocheckpoint_latency.test.metadata.json b/parser/testdata/corpus/sql/pragma/profiling/test_autocheckpoint_latency.test.metadata.json index a9881e1..6cbfb58 100644 --- a/parser/testdata/corpus/sql/pragma/profiling/test_autocheckpoint_latency.test.metadata.json +++ b/parser/testdata/corpus/sql/pragma/profiling/test_autocheckpoint_latency.test.metadata.json @@ -1,6 +1,6 @@ { "todo": { - "1506f00102367993": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "e771a88a05ba1459": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "1506f00102367993": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "e771a88a05ba1459": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/pragma/profiling/test_checkpoint_profiling.test.metadata.json b/parser/testdata/corpus/sql/pragma/profiling/test_checkpoint_profiling.test.metadata.json index ac8db13..842192a 100644 --- a/parser/testdata/corpus/sql/pragma/profiling/test_checkpoint_profiling.test.metadata.json +++ b/parser/testdata/corpus/sql/pragma/profiling/test_checkpoint_profiling.test.metadata.json @@ -1,8 +1,8 @@ { "todo": { - "4e1a908b16d2d263": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "9a8ccbb4bc80bdf7": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "af1ea835ee327dd8": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "c177711e16dcd525": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "4e1a908b16d2d263": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "9a8ccbb4bc80bdf7": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "af1ea835ee327dd8": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "c177711e16dcd525": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/pragma/profiling/test_commit_write_wal_latency_and_count.test.metadata.json b/parser/testdata/corpus/sql/pragma/profiling/test_commit_write_wal_latency_and_count.test.metadata.json index af3ff99..07f515f 100644 --- a/parser/testdata/corpus/sql/pragma/profiling/test_commit_write_wal_latency_and_count.test.metadata.json +++ b/parser/testdata/corpus/sql/pragma/profiling/test_commit_write_wal_latency_and_count.test.metadata.json @@ -1,6 +1,6 @@ { "todo": { - "f3771c8b33bec4e9": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "fce1da9a57227ed2": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "f3771c8b33bec4e9": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "fce1da9a57227ed2": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/pragma/profiling/test_custom_profiling_disable_metrics.test.metadata.json b/parser/testdata/corpus/sql/pragma/profiling/test_custom_profiling_disable_metrics.test.metadata.json index b3839fc..d0de790 100644 --- a/parser/testdata/corpus/sql/pragma/profiling/test_custom_profiling_disable_metrics.test.metadata.json +++ b/parser/testdata/corpus/sql/pragma/profiling/test_custom_profiling_disable_metrics.test.metadata.json @@ -1,5 +1,5 @@ { "todo": { - "1bf64e38825d2e20": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "1bf64e38825d2e20": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/pragma/profiling/test_duckdb_profiling_settings_function.test.metadata.json b/parser/testdata/corpus/sql/pragma/profiling/test_duckdb_profiling_settings_function.test.metadata.json index 1332ee5..83027ae 100644 --- a/parser/testdata/corpus/sql/pragma/profiling/test_duckdb_profiling_settings_function.test.metadata.json +++ b/parser/testdata/corpus/sql/pragma/profiling/test_duckdb_profiling_settings_function.test.metadata.json @@ -1,5 +1,5 @@ { "todo": { - "0d20feb1c2e289a6": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "0d20feb1c2e289a6": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/pragma/profiling/test_logging_interaction.test.metadata.json b/parser/testdata/corpus/sql/pragma/profiling/test_logging_interaction.test.metadata.json index 5a7e4f7..3c4146e 100644 --- a/parser/testdata/corpus/sql/pragma/profiling/test_logging_interaction.test.metadata.json +++ b/parser/testdata/corpus/sql/pragma/profiling/test_logging_interaction.test.metadata.json @@ -1,5 +1,5 @@ { "todo": { - "bc519dcc03daf997": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "bc519dcc03daf997": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/pragma/profiling/test_no_reset_setting.test.metadata.json b/parser/testdata/corpus/sql/pragma/profiling/test_no_reset_setting.test.metadata.json index a904794..389372c 100644 --- a/parser/testdata/corpus/sql/pragma/profiling/test_no_reset_setting.test.metadata.json +++ b/parser/testdata/corpus/sql/pragma/profiling/test_no_reset_setting.test.metadata.json @@ -1,5 +1,5 @@ { "todo": { - "cd037695c2fbba16": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "cd037695c2fbba16": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/pragma/profiling/test_profiling_output_file_overwrite.test.metadata.json b/parser/testdata/corpus/sql/pragma/profiling/test_profiling_output_file_overwrite.test.metadata.json index e6f09c8..af6b8e2 100644 --- a/parser/testdata/corpus/sql/pragma/profiling/test_profiling_output_file_overwrite.test.metadata.json +++ b/parser/testdata/corpus/sql/pragma/profiling/test_profiling_output_file_overwrite.test.metadata.json @@ -1,6 +1,6 @@ { "todo": { - "1603dedee627838d": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "21f537269ccbd0b5": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "1603dedee627838d": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "21f537269ccbd0b5": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/pragma/test_enable_profile.test.metadata.json b/parser/testdata/corpus/sql/pragma/test_enable_profile.test.metadata.json index 9331fb2..b2ae655 100644 --- a/parser/testdata/corpus/sql/pragma/test_enable_profile.test.metadata.json +++ b/parser/testdata/corpus/sql/pragma/test_enable_profile.test.metadata.json @@ -1,5 +1,5 @@ { "todo": { - "864fb44520179e80": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "864fb44520179e80": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/pragma/test_memory_limit.test.metadata.json b/parser/testdata/corpus/sql/pragma/test_memory_limit.test.metadata.json index a428b51..9d8f760 100644 --- a/parser/testdata/corpus/sql/pragma/test_memory_limit.test.metadata.json +++ b/parser/testdata/corpus/sql/pragma/test_memory_limit.test.metadata.json @@ -1,10 +1,10 @@ { "todo": { - "0d5d26c75bb47c33": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "8868ad02aeb4b7fc": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "908f3a0729f66a0b": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "ae6d8be7e8f2577e": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "bedc8dd4f19175dd": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "c275efc796943d1c": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "0d5d26c75bb47c33": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "8868ad02aeb4b7fc": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "908f3a0729f66a0b": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "ae6d8be7e8f2577e": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "bedc8dd4f19175dd": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "c275efc796943d1c": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/pragma/test_table_info_quoted_names.test.metadata.json b/parser/testdata/corpus/sql/pragma/test_table_info_quoted_names.test.metadata.json index b005363..b7d6382 100644 --- a/parser/testdata/corpus/sql/pragma/test_table_info_quoted_names.test.metadata.json +++ b/parser/testdata/corpus/sql/pragma/test_table_info_quoted_names.test.metadata.json @@ -1,9 +1,9 @@ { "todo": { - "223d2294dffb81c9": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "49ac3a030f9e967d": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "aa6c6e328a511bfe": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "dd035f1d606b9579": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "f5e9e8921ceeb4c3": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "223d2294dffb81c9": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "49ac3a030f9e967d": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "aa6c6e328a511bfe": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "dd035f1d606b9579": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "f5e9e8921ceeb4c3": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/projection/select_star_exclude.test.metadata.json b/parser/testdata/corpus/sql/projection/select_star_exclude.test.metadata.json deleted file mode 100644 index 487029c..0000000 --- a/parser/testdata/corpus/sql/projection/select_star_exclude.test.metadata.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "todo": { - "22eeceee377664af": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "46579ff7104850c8": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "7621034a2be8e898": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "e3955ac3c8ea8bdf": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/projection/select_star_rename.test.metadata.json b/parser/testdata/corpus/sql/projection/select_star_rename.test.metadata.json deleted file mode 100644 index 28035b3..0000000 --- a/parser/testdata/corpus/sql/projection/select_star_rename.test.metadata.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "todo": { - "31eb836f73ef445b": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "4958a48ec9cb90dc": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/projection/select_star_replace.test.metadata.json b/parser/testdata/corpus/sql/projection/select_star_replace.test.metadata.json deleted file mode 100644 index a2c7f17..0000000 --- a/parser/testdata/corpus/sql/projection/select_star_replace.test.metadata.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "todo": { - "57d617a3f910b633": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "8024a9b092584d5b": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/projection/test_scalar_projection.test.metadata.json b/parser/testdata/corpus/sql/projection/test_scalar_projection.test.metadata.json deleted file mode 100644 index f6e2580..0000000 --- a/parser/testdata/corpus/sql/projection/test_scalar_projection.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "6559f107dc71a9f1": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/projection/test_value_list.test.metadata.json b/parser/testdata/corpus/sql/projection/test_value_list.test.metadata.json deleted file mode 100644 index 5e97bce..0000000 --- a/parser/testdata/corpus/sql/projection/test_value_list.test.metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "todo": { - "47f657e68ce0d68b": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "81dc2217bd936b5f": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "95f244c05d756c13": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/sample/test_sample_too_big.test.metadata.json b/parser/testdata/corpus/sql/sample/test_sample_too_big.test.metadata.json deleted file mode 100644 index a22c946..0000000 --- a/parser/testdata/corpus/sql/sample/test_sample_too_big.test.metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "todo": { - "3d93e9ac0604a93a": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "576e9054512e0499": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "79b103507c279c56": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/secrets/create_secret_expression.test.metadata.json b/parser/testdata/corpus/sql/secrets/create_secret_expression.test.metadata.json deleted file mode 100644 index b24399a..0000000 --- a/parser/testdata/corpus/sql/secrets/create_secret_expression.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "00565c04938f8649": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/select/test_positional_reference.test.metadata.json b/parser/testdata/corpus/sql/select/test_positional_reference.test.metadata.json deleted file mode 100644 index 76c44fb..0000000 --- a/parser/testdata/corpus/sql/select/test_positional_reference.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "792528bd64867f08": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/settings/setting_disabled_optimizer.test.metadata.json b/parser/testdata/corpus/sql/settings/setting_disabled_optimizer.test.metadata.json index bb456eb..c48e486 100644 --- a/parser/testdata/corpus/sql/settings/setting_disabled_optimizer.test.metadata.json +++ b/parser/testdata/corpus/sql/settings/setting_disabled_optimizer.test.metadata.json @@ -1,6 +1,6 @@ { "todo": { - "0b7b9ef146cccc48": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "9579b94653bbead9": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "0b7b9ef146cccc48": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work", + "9579b94653bbead9": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/settings/setting_profiling_mode.test.metadata.json b/parser/testdata/corpus/sql/settings/setting_profiling_mode.test.metadata.json index 616dcd7..84d2967 100644 --- a/parser/testdata/corpus/sql/settings/setting_profiling_mode.test.metadata.json +++ b/parser/testdata/corpus/sql/settings/setting_profiling_mode.test.metadata.json @@ -1,5 +1,5 @@ { "todo": { - "788098fc8dfb03d5": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "788098fc8dfb03d5": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/storage/attach_mmap.test.metadata.json b/parser/testdata/corpus/sql/storage/attach_mmap.test.metadata.json index ff99177..e782153 100644 --- a/parser/testdata/corpus/sql/storage/attach_mmap.test.metadata.json +++ b/parser/testdata/corpus/sql/storage/attach_mmap.test.metadata.json @@ -1,5 +1,5 @@ { "todo": { - "90777abc828a93d8": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "90777abc828a93d8": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/storage/rowid_gaps/indexed_live_vacuum_no_rebuild.test.metadata.json b/parser/testdata/corpus/sql/storage/rowid_gaps/indexed_live_vacuum_no_rebuild.test.metadata.json index ed68d71..7c035b4 100644 --- a/parser/testdata/corpus/sql/storage/rowid_gaps/indexed_live_vacuum_no_rebuild.test.metadata.json +++ b/parser/testdata/corpus/sql/storage/rowid_gaps/indexed_live_vacuum_no_rebuild.test.metadata.json @@ -1,5 +1,5 @@ { "todo": { - "b4ebde4c163973d5": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "b4ebde4c163973d5": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/trigger/trigger_referencing_validation.test.metadata.json b/parser/testdata/corpus/sql/trigger/trigger_referencing_validation.test.metadata.json deleted file mode 100644 index 2f2bb0e..0000000 --- a/parser/testdata/corpus/sql/trigger/trigger_referencing_validation.test.metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "todo": { - "64879408d841181f": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "8e41ddf0dc8ffc0f": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "f06f8b423baeecb6": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/types/geo/geometry_crs.test.metadata.json b/parser/testdata/corpus/sql/types/geo/geometry_crs.test.metadata.json deleted file mode 100644 index 7f718c4..0000000 --- a/parser/testdata/corpus/sql/types/geo/geometry_crs.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "38b254ab655e2b4d": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/types/geo/geometry_parquet_pushdown.test.metadata.json b/parser/testdata/corpus/sql/types/geo/geometry_parquet_pushdown.test.metadata.json index 08129c1..2a074e6 100644 --- a/parser/testdata/corpus/sql/types/geo/geometry_parquet_pushdown.test.metadata.json +++ b/parser/testdata/corpus/sql/types/geo/geometry_parquet_pushdown.test.metadata.json @@ -1,5 +1,5 @@ { "todo": { - "6d655e14266461ae": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" + "6d655e14266461ae": "oracle's Parser Error comes from outside the parse pipeline (bind-time setting/function/constraint validation or nested SQL parsing); darkwing accepts by design - revisit with milestone 6 error-fidelity work" } } diff --git a/parser/testdata/corpus/sql/types/nested/array/array_invalid.test.metadata.json b/parser/testdata/corpus/sql/types/nested/array/array_invalid.test.metadata.json deleted file mode 100644 index f6b4873..0000000 --- a/parser/testdata/corpus/sql/types/nested/array/array_invalid.test.metadata.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "todo": { - "0f113ab0c062fe35": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "d589194f95624c58": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/types/struct/update_empty_row.test.metadata.json b/parser/testdata/corpus/sql/types/struct/update_empty_row.test.metadata.json deleted file mode 100644 index 81a2428..0000000 --- a/parser/testdata/corpus/sql/types/struct/update_empty_row.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "72cdb7d3a7763501": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/types/test_user_type_errors_18452.test.metadata.json b/parser/testdata/corpus/sql/types/test_user_type_errors_18452.test.metadata.json deleted file mode 100644 index 75e2ee3..0000000 --- a/parser/testdata/corpus/sql/types/test_user_type_errors_18452.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "cd46323b2b96778c": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/types/timestamp/timestamp_precision.test.metadata.json b/parser/testdata/corpus/sql/types/timestamp/timestamp_precision.test.metadata.json deleted file mode 100644 index 494a4d2..0000000 --- a/parser/testdata/corpus/sql/types/timestamp/timestamp_precision.test.metadata.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "todo": { - "ae7b463600077701": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "f6204ac9392856de": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/update/test_multiple_assignment.test.metadata.json b/parser/testdata/corpus/sql/update/test_multiple_assignment.test.metadata.json deleted file mode 100644 index a951546..0000000 --- a/parser/testdata/corpus/sql/update/test_multiple_assignment.test.metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "todo": { - "1af980d640a6e1d3": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "308b68804dd4a67f": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "c417249dc9b098ac": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/window/test_invalid_window.test.metadata.json b/parser/testdata/corpus/sql/window/test_invalid_window.test.metadata.json deleted file mode 100644 index 85e42c0..0000000 --- a/parser/testdata/corpus/sql/window/test_invalid_window.test.metadata.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "todo": { - "a944cdc99be3a290": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "b01e96e0203c6ba1": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/window/test_order_by_all.test.metadata.json b/parser/testdata/corpus/sql/window/test_order_by_all.test.metadata.json deleted file mode 100644 index 346522d..0000000 --- a/parser/testdata/corpus/sql/window/test_order_by_all.test.metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "todo": { - "4fb9fdcecdc6a6db": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/window/test_scalar_window.test.metadata.json b/parser/testdata/corpus/sql/window/test_scalar_window.test.metadata.json deleted file mode 100644 index 151055c..0000000 --- a/parser/testdata/corpus/sql/window/test_scalar_window.test.metadata.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "todo": { - "2ad67376335b80c3": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "76691012431cffdd": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/testdata/corpus/sql/window/test_window_clause.test.metadata.json b/parser/testdata/corpus/sql/window/test_window_clause.test.metadata.json deleted file mode 100644 index 464461c..0000000 --- a/parser/testdata/corpus/sql/window/test_window_clause.test.metadata.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "todo": { - "84dedaaf2db41fc2": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "9e4603bc2eea26a4": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "c4a0f47f624c993b": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)", - "ea6eacac12b584cf": "transformer-raised parser error: upstream rejects in the transformer, not the grammar; expected to clear as transformers land (milestones 3-5)" - } -} diff --git a/parser/transform.go b/parser/transform.go index 75387f3..ea60f5c 100644 --- a/parser/transform.go +++ b/parser/transform.go @@ -1,11 +1,13 @@ // transform.go: the transformer registry and shared transform state — the // port of upstream's PEGTransformerFactory (transformer/peg_transformer.cpp). -// One transform function per grammar rule, registered by rule name; -// rules not in the registry are either consumed structurally by their -// parent's transformer or belong to later milestones (ErrUnsupported). +// One transform function per grammar rule, registered by rule name; since +// milestone 5 every Statement alternative is registered, and rules not in +// the registry are consumed structurally by their parent's transformer. package parser import ( + "strings" + "github.com/sqlc-dev/darkwing/ast" ) @@ -24,6 +26,29 @@ type transformContext struct { // windows maps named WINDOW definitions of the innermost SELECT // (stack, innermost last). windows []map[string]*ast.WindowExpression + + // inWindowDefinition is set while transforming a window frame + // definition; window functions are not allowed inside one. + inWindowDefinition bool + + // pivotEntries counts PIVOT columns whose values must be extracted + // from the data (no IN list, no enum) — upstream's pivot_entries, + // kept as a count since darkwing does not expand them into enum + // creation statements. pivotEntryHasParams records whether prepared + // parameters had been seen when such an entry was recorded. + pivotEntries int + pivotEntryHasParams bool +} + +// pivotEntryCheck ports PEGTransformer::PivotEntryCheck: CREATE VIEW and +// CREATE MACRO bodies cannot contain pivots whose values come from the +// data. +func (tc *transformContext) pivotEntryCheck(kind string) { + if tc.pivotEntries > 0 { + raise("PIVOT statements with pivot elements extracted from the data cannot be used in %ss.\n"+ + "In order to use PIVOT in a %s the PIVOT values must be manually specified, e.g.:\n"+ + "PIVOT ... ON ... IN (val1, val2, ...)", kind, kind) + } } func newTransformContext(src string) *transformContext { @@ -45,57 +70,17 @@ func register(rule string, fn stmtTransform) { statementTransforms[rule] = fn } -// unsupportedStatements names the Statement alternatives that belong to -// milestone 5; the engine accepts them but Parse reports ErrUnsupported. -var unsupportedStatements = map[string]bool{ - "ExternalResourceStatement": true, // INSTALL/LOAD/UPDATE EXTENSIONS/... - "SetStatement": true, - "PragmaStatement": true, - "CallStatement": true, - "CopyStatement": true, - "ExplainStatement": true, - "PrepareStatement": true, - "ExecuteStatement": true, - "TransactionStatement": true, - "AttachStatement": true, - "UseStatement": true, - "DetachStatement": true, - "CheckpointStatement": true, - "VacuumStatement": true, - "ResetStatement": true, - "ExportStatement": true, - "ImportStatement": true, - "CommentStatement": true, - "DeallocateStatement": true, - "LoadStatement": true, - "InstallStatement": true, - "UpdateExtensionsStatement": true, - "AnalyzeStatement": true, - "ConnectStatement": true, - "DisconnectStatement": true, -} - // transformStatement dispatches one Statement node (LIST(Statement) // wrapping a choice of statement rules). func (tc *transformContext) transformStatement(n tnode) ast.Stmt { _, inner := n.sole().choice() - name := inner.name() - if fn, ok := statementTransforms[name]; ok { + if fn, ok := statementTransforms[inner.name()]; ok { return fn(tc, inner) } - if unsupportedStatements[name] { - panic(&internalErrorUnsupported{rule: name}) - } shapeError(inner, "no statement transform registered") return nil } -// internalErrorUnsupported is panicked for milestone-5 statements and -// converted to *unsupportedError at the Parse boundary. -type internalErrorUnsupported struct { - rule string -} - // finishStatement attaches the collected parameter map to the statement. func (tc *transformContext) finishStatement(stmt ast.Stmt) { if len(tc.paramOrder) == 0 { @@ -124,6 +109,26 @@ func (tc *transformContext) finishStatement(stmt ast.Stmt) { s.NamedParams = params case *ast.AlterStatement: s.NamedParams = params + case *ast.SetStatement: + s.NamedParams = params + case *ast.PragmaStatement: + s.NamedParams = params + case *ast.CallStatement: + s.NamedParams = params + case *ast.ExplainStatement: + s.NamedParams = params + case *ast.ExecuteStatement: + s.NamedParams = params + case *ast.CopyStatement: + s.NamedParams = params + case *ast.AttachStatement: + s.NamedParams = params + case *ast.ConnectStatement: + s.NamedParams = params + case *ast.ExportStatement: + s.NamedParams = params + case *ast.ExternalResourceStatement: + s.NamedParams = params } } @@ -151,9 +156,9 @@ func (tc *transformContext) registerParam(p *ast.ParameterExpression) { p.Number = tc.paramCount } } - if tc.hasNamed && tc.hasPosition { - raise("Mixing named and positional parameters is not supported yet") - } + // mixing named and positional parameters raises NotImplemented + // upstream (post-parse for the corpus oracle), so darkwing keeps + // numbering and accepts id := p.Identifier() if _, ok := tc.paramSeen[id]; !ok { tc.paramSeen[id] = p.Number @@ -171,10 +176,12 @@ func (tc *transformContext) popWindowScope() { tc.windows = tc.windows[:len(tc.windows)-1] } -// namedWindow resolves a window name in the innermost scope. +// namedWindow resolves a window name in the innermost scope (window +// names are case-insensitive identifiers). func (tc *transformContext) namedWindow(name string) *ast.WindowExpression { + key := strings.ToLower(name) for i := len(tc.windows) - 1; i >= 0; i-- { - if w, ok := tc.windows[i][name]; ok { + if w, ok := tc.windows[i][key]; ok { return w } } @@ -186,8 +193,9 @@ func (tc *transformContext) defineWindow(name string, w *ast.WindowExpression) { tc.pushWindowScope() } scope := tc.windows[len(tc.windows)-1] - if _, dup := scope[name]; dup { + key := strings.ToLower(name) + if _, dup := scope[key]; dup { raise("window \"%s\" is already defined", name) } - scope[name] = w + scope[key] = w } diff --git a/parser/transform_copy.go b/parser/transform_copy.go new file mode 100644 index 0000000..de95ece --- /dev/null +++ b/parser/transform_copy.go @@ -0,0 +1,444 @@ +// transform_copy.go: COPY ... TO/FROM (with the generic and PostgreSQL- +// style specialized option lists) and COPY FROM DATABASE. The generic +// option machinery here is shared by ATTACH, CONNECT, EXPORT, EXPLAIN, +// CREATE SECRET and the external resource statements. Port of upstream's +// transformer/transform_copy.cpp and transform_generic_copy_option.cpp. +package parser + +import ( + "strings" + + "github.com/sqlc-dev/darkwing/ast" +) + +func init() { + register("CopyStatement", func(tc *transformContext, n tnode) ast.Stmt { + return tc.transformCopy(n) + }) +} + +// ---- generic options --------------------------------------------------- + +// setGenericOptionExpr folds one option argument, mirroring upstream's +// SetGenericCopyOptionExpression: constants and bare identifiers become +// values, `*` becomes the value "*", boolean-ish casts of 't'/'f' fold to +// booleans, anything else is kept as an expression (upstream raises +// NotImplemented post-parse for genuinely unknown shapes). +func setGenericOptionExpr(opt *ast.GenericOption, e ast.Expr) { + switch v := e.(type) { + case *ast.ConstantExpression: + opt.Values = append(opt.Values, v.Value) + case *ast.ColumnRefExpression: + opt.Values = append(opt.Values, varcharValue(v.ColumnNames[len(v.ColumnNames)-1])) + case *ast.StarExpression: + opt.Values = append(opt.Values, varcharValue("*")) + case *ast.CastExpression: + if c, isConst := v.Child.(*ast.ConstantExpression); isConst && c.Value.Kind == ast.ValueString { + switch c.Value.Str { + case "t": + opt.Values = append(opt.Values, boolValue(true)) + return + case "f": + opt.Values = append(opt.Values, boolValue(false)) + return + } + } + opt.Expr = e + default: + opt.Expr = e + } +} + +func boolValue(b bool) ast.Value { + return ast.Value{Type: ast.LogicalType{ID: "BOOLEAN"}, Kind: ast.ValueBool, Bool: b} +} + +// applyGenericOptionValue maps a GenericCopyOptionValue onto the option. +// GenericCopyOptionValue <- GenericCopyOptionOrderList / GenericCopyOptionExpression +func (tc *transformContext) applyGenericOptionValue(opt *ast.GenericOption, n tnode) { + _, alt := n.sole().choice() + switch alt.name() { + case "GenericCopyOptionOrderList": + // Parens(OrderByExpressionList): a parenthesized argument list, + // parsed as order-by entries so ORDER_BY can carry modifiers. + var orders []ast.OrderByNode + for _, e := range alt.sole().parens().listElems() { + orders = append(orders, ast.OrderByNode{ + Type: orderTypeOf(e.child(1)), + NullOrder: nullOrderOf(e.child(2)), + Expression: tc.transformExpression(e.child(0)), + }) + } + hasModifier := false + for _, o := range orders { + if o.Type != ast.OrderDefault || o.NullOrder != ast.NullOrderDefault { + hasModifier = true + break + } + } + if strings.EqualFold(opt.Name, "order_by") { + // upstream renders each entry to text; darkwing keeps the + // expressions (modifiers recorded on the row arguments only) + opt.Expr = rowFunction(orderExprs(orders)) + return + } + if hasModifier { + raise("ORDER BY modifiers are only supported in the ORDER BY option") + } + if len(orders) == 1 { + setGenericOptionExpr(opt, orders[0].Expression) + return + } + opt.Expr = rowFunction(orderExprs(orders)) + case "GenericCopyOptionExpression": + setGenericOptionExpr(opt, tc.transformExpression(alt.sole())) + default: + shapeError(alt, "unknown option value") + } +} + +func orderExprs(orders []ast.OrderByNode) []ast.Expr { + out := make([]ast.Expr, len(orders)) + for i := range orders { + out[i] = orders[i].Expression + } + return out +} + +func rowFunction(args []ast.Expr) *ast.FunctionExpression { + return fnCall(invalidSpan, "row", args...) +} + +// transformGenericOption maps GenericCopyOption <- CopyOptionName GenericCopyOptionValue? +func (tc *transformContext) transformGenericOption(n tnode) ast.GenericOption { + opt := ast.GenericOption{Name: strings.ToLower(identifierText(n.child(0)))} + if value, ok := n.child(1).opt(); ok { + tc.applyGenericOptionValue(&opt, value) + } + return opt +} + +// transformGenericOptionList unwraps GenericCopyOptionList +// (Parens(List(GenericCopyOption))). +func (tc *transformContext) transformGenericOptionList(n tnode) []ast.GenericOption { + var out []ast.GenericOption + for _, o := range n.parens().listElems() { + out = append(out, tc.transformGenericOption(o)) + } + return out +} + +// ---- COPY option lists ------------------------------------------------- + +// transformCopyOptions maps CopyOptions ('WITH'? CopyOptionList). +func (tc *transformContext) transformCopyOptions(n tnode) []ast.GenericOption { + _, alt := n.child(1).sole().choice() + switch alt.name() { + case "CopyGenericOptionList": + // Parens(List(CopyGenericOption)) + var out []ast.GenericOption + for _, o := range alt.sole().parens().listElems() { + out = append(out, tc.transformCopyGenericOption(o)) + } + return out + case "SpecializedOptionList": + // SpecializedOption SpecializedOptionTail* + out := []ast.GenericOption{tc.transformSpecializedOption(alt.child(0))} + for _, tail := range alt.child(1).repeat() { + // SpecializedOptionTail <- ','? SpecializedOption + out = append(out, tc.transformSpecializedOption(tail.child(1))) + } + return out + } + shapeError(alt, "unknown copy option list") + return nil +} + +// CopyGenericOption <- OrderByCopyOption / PartitionedByCopyOption / GenericCopyOption +func (tc *transformContext) transformCopyGenericOption(n tnode) ast.GenericOption { + _, alt := n.sole().choice() + switch alt.name() { + case "OrderByCopyOption": + // 'ORDER' 'BY' GenericCopyOptionValue? + opt := ast.GenericOption{Name: "order_by"} + if value, ok := alt.child(2).opt(); ok { + tc.applyGenericOptionValue(&opt, value) + } + return opt + case "PartitionedByCopyOption": + // ('PARTITION' / 'PARTITIONED') 'BY' GenericCopyOptionValue? + opt := ast.GenericOption{Name: "partition_by"} + if value, ok := alt.child(2).opt(); ok { + tc.applyGenericOptionValue(&opt, value) + } + return opt + case "GenericCopyOption": + return tc.transformGenericOption(alt) + } + shapeError(alt, "unknown copy generic option") + return ast.GenericOption{} +} + +// transformSpecializedOption maps the PostgreSQL-style COPY option +// spellings onto generic options, mirroring the Transform*Option family. +func (tc *transformContext) transformSpecializedOption(n tnode) ast.GenericOption { + _, alt := n.sole().choice() + switch alt.name() { + case "SingleOption": + _, single := alt.sole().choice() + switch single.name() { + case "BinaryOption": + return ast.GenericOption{Name: "format", Values: []ast.Value{varcharValue("binary")}} + case "FreezeOption": + return ast.GenericOption{Name: "freeze", Values: []ast.Value{nullValue()}} + case "OidsOption": + return ast.GenericOption{Name: "oids", Values: []ast.Value{nullValue()}} + case "CsvOption": + return ast.GenericOption{Name: "format", Values: []ast.Value{varcharValue("csv")}} + case "HeaderOption": + return ast.GenericOption{Name: "header", Values: []ast.Value{boolValue(true)}} + } + shapeError(single, "unknown single copy option") + case "NullAsOption": + // 'NULL' 'AS'? StringLiteral + return ast.GenericOption{Name: "null", Values: []ast.Value{varcharValue(identifierText(alt.child(2)))}} + case "DelimiterAsOption": + return ast.GenericOption{Name: "delimiter", Values: []ast.Value{varcharValue(identifierText(alt.child(2)))}} + case "QuoteAsOption": + return ast.GenericOption{Name: "quote", Values: []ast.Value{varcharValue(identifierText(alt.child(2)))}} + case "EscapeAsOption": + return ast.GenericOption{Name: "escape", Values: []ast.Value{varcharValue(identifierText(alt.child(2)))}} + case "EncodingOption": + // 'ENCODING' StringLiteral + return ast.GenericOption{Name: "encoding", Values: []ast.Value{varcharValue(identifierText(alt.child(1)))}} + case "ForceQuoteOption": + // ForceQuote? 'QUOTE' StarSymbolColumnList + opt := ast.GenericOption{Name: "quote"} + if _, force := alt.child(0).opt(); force { + opt.Name = "force_quote" + } + applyStarOrColumns(&opt, alt.child(2)) + return opt + case "PartitionByOption": + // ('PARTITION' / 'PARTITIONED') 'BY' PartitionByColumnList + opt := ast.GenericOption{Name: "partition_by"} + _, list := alt.child(2).sole().choice() + switch list.name() { + case "StarPartitionByColumnList": + star := &ast.StarExpression{} + star.SetSpan(invalidSpan) + opt.Expr = star + case "ParenthesizedPartitionByColumnList": + for _, col := range identifierList(list.sole().parens()) { + opt.Values = append(opt.Values, varcharValue(col)) + } + case "SinglePartitionByColumnList": + opt.Values = []ast.Value{varcharValue(identifierText(list.sole()))} + default: + shapeError(list, "unknown partition-by column list") + } + return opt + case "ForceNullOption": + // 'FORCE' ForceNotNull? 'NULL' ColumnList + opt := ast.GenericOption{Name: "force_null"} + if _, notNull := alt.child(1).opt(); notNull { + opt.Name = "force_not_null" + } + for _, col := range identifierList(alt.child(3)) { + opt.Values = append(opt.Values, varcharValue(col)) + } + return opt + } + shapeError(alt, "unknown specialized copy option") + return ast.GenericOption{} +} + +// applyStarOrColumns maps StarSymbolColumnList (StarSymbol / ColumnList): +// `*` becomes a star expression, a column list becomes values. +func applyStarOrColumns(opt *ast.GenericOption, n tnode) { + _, alt := n.sole().choice() + if alt.name() == "StarSymbol" { + star := &ast.StarExpression{} + star.SetSpan(invalidSpan) + opt.Expr = star + return + } + for _, col := range identifierList(alt) { + opt.Values = append(opt.Values, varcharValue(col)) + } +} + +func nullValue() ast.Value { + return ast.Value{Type: ast.LogicalType{ID: "NULL"}, IsNull: true, Kind: ast.ValueNull} +} + +// setCopyOptions folds an option list onto the CopyInfo, mirroring +// upstream's SetCopyOptions: PARTITIONED_BY is normalized, duplicates are +// a parser error, and a constant-valued FORMAT option is extracted. +func setCopyOptions(info *ast.CopyInfo, opts []ast.GenericOption) { + seen := map[string]bool{} + for i := range opts { + if strings.EqualFold(opts[i].Name, "partitioned_by") { + opts[i].Name = "partition_by" + } + key := strings.ToLower(opts[i].Name) + if seen[key] { + raise("Unexpected duplicate option %s", opts[i].Name) + } + seen[key] = true + } + kept := opts[:0] + for _, o := range opts { + if strings.EqualFold(o.Name, "format") && o.Expr == nil { + if len(o.Values) == 0 { + raise("Unsupported parameter type for FORMAT: expected e.g. FORMAT 'csv', 'parquet'") + } + info.Format = o.Values[0].Str + info.FormatExplicit = true + continue + } + kept = append(kept, o) + } + info.Options = kept +} + +// extractFormat derives the format from a file extension, mirroring +// upstream's ExtractFormat (compression suffixes stripped first). +func extractFormat(path string) string { + f := strings.ToLower(path) + if strings.HasSuffix(f, ".gz") { + f = f[:len(f)-3] + } else if strings.HasSuffix(f, ".zst") { + f = f[:len(f)-4] + } + dot := strings.LastIndexByte(f, '.') + if dot < 0 || dot == len(f)-1 { + return "" + } + return f[dot+1:] +} + +// ---- the COPY statement ------------------------------------------------ + +// transformCopyFileName maps CopyFileName into a literal path or a path +// expression. +func (tc *transformContext) transformCopyFileName(n tnode) (string, ast.Expr) { + _, alt := n.sole().choice() + switch alt.name() { + case "CopyFileNameExpression": + // Parameter / ParensExpression + _, inner := alt.sole().choice() + var e ast.Expr + if inner.name() == "Parameter" { + e = tc.transformParameter(inner) + } else { + e = tc.transformExpression(inner.parens()) + } + if c, isConst := e.(*ast.ConstantExpression); isConst && c.Value.Kind == ast.ValueString { + return c.Value.Str, nil + } + return "", e + case "CopyFileNameStringLiteral": + return identifierText(alt.sole()), nil + case "CopyFileNameIdentifier": + id := identifierText(alt.sole()) + if strings.EqualFold(id, "stdout") { + return "/dev/stdout", nil + } + return id, nil + case "CopyFileNameIdentifierColId": + // IdentifierColId <- Identifier '.' ColId + ic := alt.sole() + return identifierText(ic.child(0)) + "." + identifierText(ic.child(2)), nil + } + shapeError(alt, "unknown copy file name") + return "", nil +} + +// CopyStatement <- 'COPY' CopyVariations +func (tc *transformContext) transformCopy(n tnode) ast.Stmt { + sp := n.span() + _, alt := n.child(1).sole().choice() + switch alt.name() { + case "CopyTable": + return tc.transformCopyTable(alt, sp) + case "CopySelect": + return tc.transformCopySelect(alt, sp) + case "CopyFromDatabase": + return tc.transformCopyFromDatabase(alt, sp) + } + shapeError(alt, "unknown copy variation") + return nil +} + +// CopyTable <- BaseTableName InsertColumnList? FromOrTo CopyFileName CopyOptions? +func (tc *transformContext) transformCopyTable(n tnode, sp ast.Span) ast.Stmt { + info := &ast.CopyInfo{} + info.SetSpan(n.span()) + ref := &ast.BaseTableRef{} + tc.fillBaseTableName(ref, n.child(0)) + info.Catalog, info.Schema, info.Table = ref.CatalogName, ref.SchemaName, ref.TableName + if cols, ok := n.child(1).opt(); ok { + info.Columns = identifierList(cols.parens()) + } + // FromOrTo <- CopyFrom / CopyTo + _, fromTo := n.child(2).sole().choice() + info.IsFrom = fromTo.name() == "CopyFrom" + info.FilePath, info.FilePathExpr = tc.transformCopyFileName(n.child(3)) + info.Format = extractFormat(info.FilePath) + if opts, ok := n.child(4).opt(); ok { + setCopyOptions(info, tc.transformCopyOptions(opts)) + } + stmt := &ast.CopyStatement{Info: info} + stmt.SetSpan(sp) + return stmt +} + +// CopySelect <- Parens(SelectStatementInternal) 'TO' CopyFileName CopyOptions? +func (tc *transformContext) transformCopySelect(n tnode, sp ast.Span) ast.Stmt { + info := &ast.CopyInfo{} + info.SetSpan(n.span()) + info.Query = tc.transformSelectInternalStatement(n.child(0).parens()).Node + info.FilePath, info.FilePathExpr = tc.transformCopyFileName(n.child(2)) + if opts, ok := n.child(3).opt(); ok { + setCopyOptions(info, tc.transformCopyOptions(opts)) + } + stmt := &ast.CopyStatement{Info: info} + stmt.SetSpan(sp) + return stmt +} + +// CopyFromDatabase <- CopyFromDatabaseWithFlag / CopyFromDatabaseWithoutFlag +func (tc *transformContext) transformCopyFromDatabase(n tnode, sp ast.Span) ast.Stmt { + _, alt := n.sole().choice() + switch alt.name() { + case "CopyFromDatabaseWithFlag": + // 'FROM' 'DATABASE' ColId 'TO' ColId CopyDatabaseFlag + stmt := &ast.CopyDatabaseStatement{ + FromDatabase: identifierText(alt.child(2)), + ToDatabase: identifierText(alt.child(4)), + } + // CopyDatabaseFlag <- Parens(SchemaOrData) + _, kind := alt.child(5).parens().sole().choice() + if kind.name() == "CopySchema" { + stmt.CopyType = ast.CopyDatabaseSchema + } else { + stmt.CopyType = ast.CopyDatabaseData + } + stmt.SetSpan(sp) + return stmt + case "CopyFromDatabaseWithoutFlag": + // upstream desugars into PRAGMA copy_database(from, to) + stmt := &ast.PragmaStatement{ + Name: "copy_database", + Parameters: []ast.Expr{ + constExpr(invalidSpan, varcharValue(identifierText(alt.child(2)))), + constExpr(invalidSpan, varcharValue(identifierText(alt.child(4)))), + }, + } + stmt.SetSpan(sp) + return stmt + } + shapeError(alt, "unknown copy-from-database form") + return nil +} diff --git a/parser/transform_create_misc.go b/parser/transform_create_misc.go new file mode 100644 index 0000000..3bdca5a --- /dev/null +++ b/parser/transform_create_misc.go @@ -0,0 +1,226 @@ +// transform_create_misc.go: the milestone-5 CREATE variants — macros, +// secrets and triggers. Port of upstream's +// transformer/transform_create_macro.cpp, transform_create_secret.cpp and +// transform_create_trigger.cpp. +package parser + +import ( + "strings" + + "github.com/sqlc-dev/darkwing/ast" +) + +// ---- CREATE MACRO ------------------------------------------------------ + +// CreateMacroStmt <- MacroOrFunction IfNotExists? QualifiedName List(MacroDefinition) +func (tc *transformContext) transformCreateMacro(n tnode) ast.CreateInfo { + info := &ast.CreateMacroInfo{} + info.SetSpan(n.span()) + applyIfNotExists(&info.CreateInfoBase, n.child(1)) + tc.fillQualifiedName(&info.CreateInfoBase, n.child(2)) + for _, def := range n.child(3).listElems() { + info.Macros = append(info.Macros, tc.transformMacroDefinition(def)) + } + info.IsTable = info.Macros[0].Query != nil + for _, m := range info.Macros[1:] { + if (m.Query != nil) != info.IsTable { + raise("Cannot mix table and scalar macro function definitions") + } + } + tc.pivotEntryCheck("macro") + return info +} + +// MacroDefinition <- Parens(MacroParameters?) 'AS' MacroDefinitionBody +func (tc *transformContext) transformMacroDefinition(n tnode) *ast.MacroFunction { + m := &ast.MacroFunction{} + m.SetSpan(n.span()) + if params, ok := n.child(0).parens().opt(); ok { + seen := map[string]bool{} + defaultSeen := false + for _, p := range params.listElems() { + mp := tc.transformMacroParameter(p) + key := strings.ToLower(mp.Name) + if seen[key] { + raise("Duplicate parameter '%s' in macro definition", mp.Name) + } + seen[key] = true + if mp.Default != nil { + defaultSeen = true + } else if defaultSeen { + raise("Parameter without a default follows parameter with a default") + } + m.Parameters = append(m.Parameters, mp) + } + } + // MacroDefinitionBody <- TableMacroDefinition / ScalarMacroDefinition + _, body := n.child(2).sole().choice() + if body.name() == "TableMacroDefinition" { + // 'TABLE' SelectStatementInternal + m.Query = tc.transformSelectInternalStatement(body.child(1)) + } else { + m.Expr = tc.transformExpression(body.sole()) + } + return m +} + +// MacroParameter <- NamedParameter / SimpleParameter +func (tc *transformContext) transformMacroParameter(n tnode) ast.MacroParameter { + _, alt := n.sole().choice() + var mp ast.MacroParameter + switch alt.name() { + case "NamedParameter": + // TypeFuncName Type? NamedParameterAssignment Expression + mp.Name = identifierText(alt.child(0)) + if t, ok := alt.child(1).opt(); ok { + mp.Type = tc.transformType(t) + } + mp.Default = tc.transformExpression(alt.child(3)) + case "SimpleParameter": + // TypeFuncName Type? + mp.Name = identifierText(alt.child(0)) + if t, ok := alt.child(1).opt(); ok { + mp.Type = tc.transformType(t) + } + default: + shapeError(alt, "unknown macro parameter") + } + return mp +} + +// ---- CREATE SECRET ----------------------------------------------------- + +// optionValueExpr is one option's value as an expression, mirroring +// GenericCopyOption::GetFirstChildOrExpression. Nil for a bare flag. +func optionValueExpr(o ast.GenericOption) ast.Expr { + if len(o.Values) > 0 { + return constExpr(invalidSpan, o.Values[0]) + } + return o.Expr +} + +// CreateSecretStmt <- 'SECRET' IfNotExists? SecretName? SecretStorageSpecifier? GenericCopyOptionList +func (tc *transformContext) transformCreateSecret(n tnode) ast.CreateInfo { + info := &ast.CreateSecretInfo{} + info.SetSpan(n.span()) + applyIfNotExists(&info.CreateInfoBase, n.child(1)) + if name, ok := n.child(2).opt(); ok { + info.Name = identifierText(name.sole()) + } + if storage, ok := n.child(3).opt(); ok { + // SecretStorageSpecifier <- 'IN' Identifier + info.Storage = strings.ToLower(identifierText(storage.child(1))) + } + for _, o := range tc.transformGenericOptionList(n.child(4)) { + switch strings.ToLower(o.Name) { + case "scope": + info.Scope = optionValueExpr(o) + case "type": + info.Type = optionValueExpr(o) + case "provider": + info.Provider = optionValueExpr(o) + default: + // duplicates raise a binder error upstream (post-parse) + info.Options = append(info.Options, o) + } + } + if info.Name == "" { + if info.Type == nil { + raise("Failed to create secret - secret must have a type defined") + } + // a non-constant type with a default name raises InvalidInput + // upstream (post-parse); darkwing leaves the name empty then + if c, ok := info.Type.(*ast.ConstantExpression); ok && c.Value.Kind == ast.ValueString { + info.Name = "__default_" + strings.ToLower(c.Value.Str) + } + } + return info +} + +// ---- CREATE TRIGGER ---------------------------------------------------- + +// CreateTriggerStmt <- 'TRIGGER' IfNotExists? TriggerName TriggerTiming +// TriggerEvent 'ON' BaseTableName ReferencingClause? ForEachClause? TriggerBody +func (tc *transformContext) transformCreateTrigger(n tnode) ast.CreateInfo { + info := &ast.CreateTriggerInfo{} + info.SetSpan(n.span()) + applyIfNotExists(&info.CreateInfoBase, n.child(1)) + info.Name = identifierText(n.child(2)) + _, timing := n.child(3).sole().choice() + switch timing.name() { + case "TriggerBefore": + info.Timing = ast.TriggerBefore + case "TriggerAfter": + info.Timing = ast.TriggerAfter + case "TriggerInsteadOf": + info.Timing = ast.TriggerInsteadOf + } + // TriggerEvent <- TriggerEventUpdateOf / TriggerEventInsert / + // TriggerEventDelete / TriggerEventUpdate + _, event := n.child(4).sole().choice() + switch event.name() { + case "TriggerEventInsert": + info.Event = ast.TriggerEventInsert + case "TriggerEventDelete": + info.Event = ast.TriggerEventDelete + case "TriggerEventUpdate": + info.Event = ast.TriggerEventUpdate + case "TriggerEventUpdateOf": + // 'UPDATE' 'OF' TriggerColumnList + info.Event = ast.TriggerEventUpdate + info.Columns = identifierList(event.child(2)) + } + table := &ast.BaseTableRef{} + table.SetSpan(n.child(6).span()) + tc.fillBaseTableName(table, n.child(6)) + info.Table = table + if ref, ok := n.child(7).opt(); ok { + tc.transformReferencingClause(info, ref) + } + if each, ok := n.child(8).opt(); ok { + _, kind := each.sole().choice() + if kind.name() == "ForEachRow" { + info.ForEach = ast.TriggerForEachRow + } else { + info.ForEach = ast.TriggerForEachStatement + } + } + // TriggerBody <- InsertStatement / UpdateStatement / DeleteStatement / + // MergeIntoStatement — the same shape as a Statement choice + info.Body = tc.transformStatement(n.child(9)) + return info +} + +// ReferencingClause <- 'REFERENCING' ReferencingItem ReferencingItem? +func (tc *transformContext) transformReferencingClause(info *ast.CreateTriggerInfo, n tnode) { + newTable, oldTable := referencingItem(n.child(1)) + info.ReferencingNew, info.ReferencingOld = newTable, oldTable + if item, ok := n.child(2).opt(); ok { + newTable, oldTable = referencingItem(item) + if newTable != "" { + if info.ReferencingNew != "" { + raise("NEW TABLE cannot be specified multiple times in REFERENCING clause") + } + info.ReferencingNew = newTable + } + if oldTable != "" { + if info.ReferencingOld != "" { + raise("OLD TABLE cannot be specified multiple times in REFERENCING clause") + } + info.ReferencingOld = oldTable + } + } + if info.ReferencingNew != "" && info.ReferencingOld != "" && info.ReferencingNew == info.ReferencingOld { + raise("REFERENCING aliases must be distinct") + } +} + +// ReferencingItem <- ReferencingNewTableAs / ReferencingOldTableAs +func referencingItem(n tnode) (newTable, oldTable string) { + _, alt := n.sole().choice() + // 'NEW'/'OLD' 'TABLE' 'AS' ColId + if alt.name() == "ReferencingNewTableAs" { + return identifierText(alt.child(3)), "" + } + return "", identifierText(alt.child(3)) +} diff --git a/parser/transform_ddl.go b/parser/transform_ddl.go index bb13c4c..8612e33 100644 --- a/parser/transform_ddl.go +++ b/parser/transform_ddl.go @@ -1,12 +1,12 @@ -// transform_ddl.go: CREATE / ALTER / DROP — the milestone-4 DDL -// transformers: tables (columns + constraints), views, schemas, indexes, -// sequences and types. CREATE MACRO / SECRET / TRIGGER belong to -// milestone 5 and report ErrUnsupported. Port of upstream's -// transformer/statement/transform_create_*.cpp, transform_alter.cpp and -// transform_drop.cpp. +// transform_ddl.go: CREATE / ALTER / DROP — the DDL transformers: tables +// (columns + constraints), views, schemas, indexes, sequences and types +// (CREATE MACRO / SECRET / TRIGGER live in transform_create_misc.go). +// Port of upstream's transformer/statement/transform_create_*.cpp, +// transform_alter.cpp and transform_drop.cpp. package parser import ( + "math" "strings" "github.com/sqlc-dev/darkwing/ast" @@ -55,8 +55,12 @@ func (tc *transformContext) transformCreate(n tnode) ast.Stmt { info = tc.transformCreateSequence(variation) case "CreateTypeStmt": info = tc.transformCreateType(variation) - case "CreateMacroStmt", "CreateSecretStmt", "CreateTriggerStmt": - panic(&internalErrorUnsupported{rule: variation.name()}) + case "CreateMacroStmt": + info = tc.transformCreateMacro(variation) + case "CreateSecretStmt": + info = tc.transformCreateSecret(variation) + case "CreateTriggerStmt": + info = tc.transformCreateTrigger(variation) default: shapeError(variation, "unknown create variation") } @@ -159,6 +163,16 @@ func (tc *transformContext) transformColumnDefinition(n tnode) ast.ColumnDef { } } } + if col.Type == nil && col.Generated == "" { + raise("Column %s must have a type or be defined as a GENERATED column.", strings.Join(col.Names, ".")) + } + if col.Generated != "" { + name := col.Names[len(col.Names)-1] + if hasSubquery(col.Default) { + raise("Expression of generated column \"%s\" contains a subquery, which isn't allowed", name) + } + verifyGeneratedColumnRefs(col.Default) + } constraintName := "" if named, ok := n.child(3).opt(); ok { constraintName = identifierText(named.child(1)) @@ -166,8 +180,16 @@ func (tc *transformContext) transformColumnDefinition(n tnode) ast.ColumnDef { for _, c := range n.child(4).repeat() { constraint := tc.transformColumnConstraint(&col, c) if constraint == nil { + if col.Generated != "" && col.Collation != "" { + raise("Collations are not supported on generated columns") + } continue } + if col.Generated != "" { + if _, isDefault := constraint.(*ast.DefaultConstraint); isDefault { + raise("Not allowed to set default on a generated column") + } + } if constraintName != "" { setConstraintName(constraint, constraintName) constraintName = "" @@ -226,7 +248,7 @@ func (tc *transformContext) transformColumnConstraint(col *ast.ColumnDef, n tnod c.SetSpan(alt.span()) return c case "CheckConstraint": - c := &ast.CheckConstraint{Expr: tc.transformExpression(alt.child(1).parens())} + c := &ast.CheckConstraint{Expr: checkConstraintExpr(tc.transformExpression(alt.child(1).parens()))} c.SetSpan(alt.span()) return c case "ForeignKeyConstraint": @@ -273,6 +295,11 @@ func (tc *transformContext) transformForeignKey(n tnode, columns []string) *ast. if da, ok := actions.child(1).opt(); ok { c.OnDelete = keyAction(da.child(2)) } + // the table-level form checks the column counts (upstream's + // TransformTopForeignKeyConstraint, after the actions transform) + if len(c.Columns) > 0 && len(c.ReferencedColumns) > 0 && len(c.Columns) != len(c.ReferencedColumns) { + raise("The number of referencing and referenced columns for foreign keys must be the same") + } return c } @@ -283,17 +310,55 @@ func keyAction(n tnode) ast.KeyAction { return ast.KeyActionNone case "RestrictKeyAction": return ast.KeyActionRestrict - case "CascadeKeyAction": - return ast.KeyActionCascade - case "SetNullKeyAction": - return ast.KeyActionSetNull - case "SetDefaultKeyAction": - return ast.KeyActionSetDefault + case "CascadeKeyAction", "SetNullKeyAction", "SetDefaultKeyAction": + // DuckDB does not support referential actions + raise("FOREIGN KEY constraints cannot use CASCADE, SET NULL or SET DEFAULT") } shapeError(alt, "unknown key action") return "" } +// verifyGeneratedColumnRefs ports VerifyColumnRefs: generated column +// expressions cannot contain qualified column references. +func verifyGeneratedColumnRefs(e ast.Expr) { + if e == nil { + return + } + if ref, ok := e.(*ast.ColumnRefExpression); ok && len(ref.ColumnNames) > 1 { + raise("Qualified (tbl.name) column references are not allowed inside of generated column expressions") + } + for _, child := range e.Children() { + if expr, ok := child.(ast.Expr); ok { + verifyGeneratedColumnRefs(expr) + } + } +} + +// hasSubquery reports whether the expression tree contains a subquery +// (upstream's ParsedExpression::HasSubquery). +func hasSubquery(e ast.Expr) bool { + if e == nil { + return false + } + if _, ok := e.(*ast.SubqueryExpression); ok { + return true + } + for _, child := range e.Children() { + if expr, ok := child.(ast.Expr); ok && hasSubquery(expr) { + return true + } + } + return false +} + +// checkConstraintExpr guards a CHECK constraint body. +func checkConstraintExpr(e ast.Expr) ast.Expr { + if hasSubquery(e) { + raise("subqueries prohibited in CHECK constraints") + } + return e +} + // TopLevelConstraint <- ConstraintNameClause? TopLevelConstraintList func (tc *transformContext) transformTopLevelConstraint(n tnode) ast.Constraint { name := "" @@ -314,7 +379,7 @@ func (tc *transformContext) transformTopLevelConstraint(n tnode) ast.Constraint out = c case "TopCheckConstraint": inner := alt.sole() - c := &ast.CheckConstraint{Expr: tc.transformExpression(inner.child(1).parens())} + c := &ast.CheckConstraint{Expr: checkConstraintExpr(tc.transformExpression(inner.child(1).parens()))} c.SetSpan(alt.span()) out = c case "TopForeignKeyConstraint": @@ -358,6 +423,7 @@ func (tc *transformContext) transformCreateView(n tnode) ast.CreateInfo { info.Aliases = identifierList(cols.sole().parens()) } info.Query = tc.transformSelectInternalStatement(n.child(7)) + tc.pivotEntryCheck("view") return info } @@ -404,19 +470,181 @@ func (tc *transformContext) transformCreateIndex(n tnode) ast.CreateInfo { return info } +// sequenceOptionKey names an option for duplicate detection (upstream's +// option pair keys). +func sequenceOptionKey(alt tnode) string { + switch alt.name() { + case "SeqSetCycle": + return "cycle" + case "SeqSetIncrement": + return "increment" + case "SeqSetMinMax": + if seqIsMin(alt.child(0)) { + return "minvalue" + } + return "maxvalue" + case "SeqNoMinMax": + if seqIsMin(alt.child(1)) { + return "nominvalue" + } + return "nomaxvalue" + case "SeqStartWith": + return "start" + case "SeqOwnedBy": + return "owned" + } + shapeError(alt, "unknown sequence option") + return "" +} + +// seqConstant requires a sequence option value to fold to a constant +// (upstream's TransformSeqSet* transforms; negated literals are already +// folded by the expression transformer). +func seqConstant(e ast.Expr) ast.Value { + c, ok := e.(*ast.ConstantExpression) + if !ok { + raise("Expected constant expression.") + } + return c.Value +} + +// seqInt extracts an int64 option value. Non-integer constants fail +// upstream with a conversion error (post-parse), so ok=false skips the +// parse-time range checks. +func seqInt(v ast.Value) (int64, bool) { + if v.Kind == ast.ValueInt64 { + return v.Int64, true + } + return 0, false +} + // CreateSequenceStmt <- 'SEQUENCE' IfNotExists? QualifiedName SequenceOption* +// Port of TransformCreateSequenceStmt, including its parse-time option +// validation: duplicates, NULL values, zero increments and the +// min/max/start range checks over the evaluated defaults. func (tc *transformContext) transformCreateSequence(n tnode) ast.CreateInfo { info := &ast.CreateSequenceInfo{} info.SetSpan(n.span()) applyIfNotExists(&info.CreateInfoBase, n.child(1)) tc.fillQualifiedName(&info.CreateInfoBase, n.child(2)) - for _, opt := range n.child(3).repeat() { - tc.applySequenceOption(info, opt) + + seen := map[string]bool{} + inc, minV, maxV, start := int64(1), int64(1), int64(math.MaxInt64), int64(0) + minSet, maxSet, startSet := false, false, false + evalOK := true + for _, o := range n.child(3).repeat() { + _, alt := o.sole().choice() + key := sequenceOptionKey(alt) + if seen[key] { + raise("%s should be passed at most once", strings.ToUpper(key[:1])+key[1:]) + } + seen[key] = true + switch alt.name() { + case "SeqSetCycle": + _, kind := alt.sole().choice() + info.Cycle = kind.name() == "SeqCycle" + case "SeqSetIncrement": + // 'INCREMENT' 'BY'? Expression + info.Increment = tc.transformExpression(alt.child(2)) + v := seqConstant(info.Increment) + if v.IsNull { + raise("INCREMENT must not be NULL") + } + if iv, ok := seqInt(v); ok { + inc = iv + if inc == 0 { + raise("Increment must not be zero") + } + } else { + evalOK = false + } + case "SeqSetMinMax": + // SeqMinOrMax Expression + expr := tc.transformExpression(alt.child(1)) + v := seqConstant(expr) + if seqIsMin(alt.child(0)) { + info.MinValue = expr + if v.IsNull { + raise("MINVALUE must not be NULL") + } + if iv, ok := seqInt(v); ok { + minV, minSet = iv, true + } else { + evalOK = false + } + } else { + info.MaxValue = expr + if v.IsNull { + raise("MAXVALUE must not be NULL") + } + if iv, ok := seqInt(v); ok { + maxV, maxSet = iv, true + } else { + evalOK = false + } + } + case "SeqNoMinMax": + // 'NO' SeqMinOrMax + if seqIsMin(alt.child(1)) { + info.NoMinValue = true + } else { + info.NoMaxValue = true + } + case "SeqStartWith": + // 'START' 'WITH'? Expression + info.StartValue = tc.transformExpression(alt.child(2)) + v := seqConstant(info.StartValue) + if v.IsNull { + raise("START value must not be NULL") + } + if iv, ok := seqInt(v); ok { + start, startSet = iv, true + } else { + evalOK = false + } + case "SeqOwnedBy": + // OWNED BY is an ALTER SEQUENCE option upstream + raise("Unrecognized option \"owned\" for CREATE SEQUENCE") + } + } + if seen["nominvalue"] && seen["minvalue"] { + raise("Minvalue should be passed at most once") + } + if seen["nomaxvalue"] && seen["maxvalue"] { + raise("Maxvalue should be passed at most once") + } + if evalOK { + if inc < 0 { + if !minSet { + minV = math.MinInt64 + } + if !maxSet { + maxV = -1 + } + } + if !startSet { + if inc < 0 { + start = maxV + } else { + start = minV + } + } + if maxV <= minV { + raise("MINVALUE (%d) must be less than MAXVALUE (%d)", minV, maxV) + } + if start < minV { + raise("START value (%d) cannot be less than MINVALUE (%d)", start, minV) + } + if start > maxV { + raise("START value (%d) cannot be greater than MAXVALUE (%d)", start, maxV) + } } return info } -// applySequenceOption maps one SequenceOption. +// applySequenceOption maps one SequenceOption for ALTER SEQUENCE. The +// option transforms still require constant values (upstream raises in the +// per-option transforms); the CREATE-only NULL/range checks do not apply. func (tc *transformContext) applySequenceOption(info *ast.CreateSequenceInfo, n tnode) { _, alt := n.sole().choice() switch alt.name() { @@ -426,9 +654,11 @@ func (tc *transformContext) applySequenceOption(info *ast.CreateSequenceInfo, n case "SeqSetIncrement": // 'INCREMENT' 'BY'? Expression info.Increment = tc.transformExpression(alt.child(2)) + seqConstant(info.Increment) case "SeqSetMinMax": // SeqMinOrMax Expression expr := tc.transformExpression(alt.child(1)) + seqConstant(expr) if seqIsMin(alt.child(0)) { info.MinValue = expr } else { @@ -444,6 +674,7 @@ func (tc *transformContext) applySequenceOption(info *ast.CreateSequenceInfo, n case "SeqStartWith": // 'START' 'WITH'? Expression info.StartValue = tc.transformExpression(alt.child(2)) + seqConstant(info.StartValue) case "SeqOwnedBy": cat, schema, name := tc.transformQualifiedNameParts(alt.child(2)) info.OwnedBy = &ast.QualifiedName{Catalog: cat, Schema: schema, Name: name} @@ -501,6 +732,9 @@ func (tc *transformContext) transformAlter(n tnode) ast.Stmt { for _, opt := range alt.child(3).listElems() { stmt.Actions = append(stmt.Actions, tc.transformAlterTableOption(opt)) } + if len(stmt.Actions) > 1 { + raise("Only one ALTER command per statement is supported") + } case "AlterViewStmt": // 'VIEW' IfExists? BaseTableName RenameAlter stmt.Entity = ast.AlterEntityView @@ -521,7 +755,15 @@ func (tc *transformContext) transformAlter(n tnode) ast.Stmt { case "SetSequenceOption": seq := &ast.SetSequenceOptionInfo{} seq.SetSpan(opts.span()) + seenOwned := false for _, o := range opts.sole().repeat() { + _, optAlt := o.sole().choice() + if optAlt.name() == "SeqOwnedBy" { + if seenOwned { + raise("Owned by value should be passed at most once") + } + seenOwned = true + } tc.applySequenceOption(&seq.Options, o) } stmt.Actions = append(stmt.Actions, seq) @@ -612,6 +854,12 @@ func (tc *transformContext) transformAlterTableOption(n tnode) ast.AlterInfo { } } } + if col.Type == nil && col.Generated == "" { + raise("Column definition requires a type or generated expression") + } + if col.Generated != "" { + raise("Adding generated columns after table creation is not supported yet") + } for _, c := range entry.child(3).repeat() { if constraint := tc.transformColumnConstraint(&col, c); constraint != nil { col.Constraints = append(col.Constraints, constraint) @@ -677,12 +925,33 @@ func (tc *transformContext) transformAlterTableOption(n tnode) ast.AlterInfo { case "ResetOptions": info := &ast.GenericAlterInfo{Kind: "RESET_OPTIONS"} info.SetSpan(alt.span()) + // RESET (...) options cannot carry values (a bare NULL passes, + // mirroring upstream's null-constant default) + for _, o := range alt.child(1).parens().listElems() { + // RelOption <- RelOptionName RelOptionArgumentOpt? + if arg, ok := o.child(1).opt(); ok { + // RelOptionArgumentOpt <- '=' DefArg + _, def := arg.child(1).sole().choice() + if def.name() != "DefArgNull" { + raise("Reset option \"%s\" cannot set any value. Did you mean to use SET?", relOptionName(o.child(0))) + } + } + } return info } shapeError(alt, "unknown alter table option") return nil } +// relOptionName reads a RelOptionName (DottedIdentifierString / StringLiteral). +func relOptionName(n tnode) string { + _, alt := n.sole().choice() + if alt.name() == "DottedIdentifierString" { + return strings.Join(dottedIdentifier(alt.sole()), ".") + } + return identifierText(alt) +} + // transformOrderByExpressionsNode maps a bare OrderByExpressions node // (used by SET SORTED BY). func (tc *transformContext) transformOrderByExpressionsNode(n tnode) []ast.OrderByNode { @@ -734,6 +1003,9 @@ func (tc *transformContext) transformAlterColumnEntry(column []string, n tnode, if using, ok := alt.child(3).opt(); ok { info.Using = tc.transformExpression(using.child(1)) } + if info.Type == nil && info.Using == nil { + raise("Omitting the type is only possible in combination with USING") + } return info } shapeError(alt, "unknown alter column entry") diff --git a/parser/transform_dml.go b/parser/transform_dml.go index fc5163b..fcfeca2 100644 --- a/parser/transform_dml.go +++ b/parser/transform_dml.go @@ -74,6 +74,9 @@ func (tc *transformContext) transformInsert(n tnode) ast.Stmt { case "SelectInsertValues": stmt.Query = tc.transformSelectInternalStatement(values.sole()) case "DefaultValues": + if len(stmt.Columns) > 0 { + raise("You can not provide both a column list and DEFAULT VALUES, please remove one of the two") + } stmt.DefaultValues = true default: shapeError(values, "unknown insert values") @@ -155,12 +158,27 @@ func (tc *transformContext) transformUpdateSetClause(n tnode) *ast.UpdateSetInfo set.Expressions = append(set.Expressions, tc.transformExpression(e.child(2))) } case "UpdateSetTuple": - // Parens(List(ColumnName)) '=' Expression: every column is - // assigned from the single row-valued expression + // Parens(List(ColumnName)) '=' Expression: a row-valued + // expression is unpacked onto the columns (with a count check); + // any other expression assigns to every column, like upstream for _, c := range alt.child(0).parens().listElems() { set.Columns = append(set.Columns, []string{identifierText(c)}) } - set.Expressions = append(set.Expressions, tc.transformExpression(alt.child(2))) + expr := tc.transformExpression(alt.child(2)) + if fn, isFn := expr.(*ast.FunctionExpression); isFn && fn.FunctionName == "row" && + fn.Schema == "" && fn.Catalog == "" { + if len(fn.Arguments) != len(set.Columns) { + raise("Could not perform assignment, expected %d values, got %d", + len(set.Columns), len(fn.Arguments)) + } + for _, a := range fn.Arguments { + set.Expressions = append(set.Expressions, a.Expr) + } + } else { + for range set.Columns { + set.Expressions = append(set.Expressions, expr) + } + } default: shapeError(alt, "unknown update set clause") } @@ -282,8 +300,19 @@ func (tc *transformContext) transformMergeInto(n tnode) ast.Stmt { default: shapeError(qual, "unknown merge join qualifier") } + unconditional := map[ast.MergeMatchKind]bool{} for _, match := range n.child(6).repeat() { - stmt.Actions = append(stmt.Actions, tc.transformMergeMatch(match)) + action := tc.transformMergeMatch(match) + // once an unconditional clause exists for a match kind, further + // clauses of that kind are unreachable + if unconditional[action.Kind] { + kind := mergeKindString(action.Kind) + raise("Unconditional %s clause was already defined - any following %s clause would be unreachable", kind, kind) + } + if action.Condition == nil { + unconditional[action.Kind] = true + } + stmt.Actions = append(stmt.Actions, action) } if ret, ok := n.child(7).opt(); ok { stmt.Returning = tc.transformReturning(ret) @@ -292,6 +321,19 @@ func (tc *transformContext) transformMergeInto(n tnode) ast.Stmt { } // MergeMatch <- MatchedClause / NotMatchedClause +// mergeKindString spells a match kind the way upstream's +// ActionConditionToString does, for the unreachable-clause error. +func mergeKindString(kind ast.MergeMatchKind) string { + switch kind { + case ast.MergeWhenMatched: + return "WHEN MATCHED" + case ast.MergeWhenNotMatchedBySource: + return "WHEN NOT MATCHED BY SOURCE" + default: + return "WHEN NOT MATCHED" + } +} + func (tc *transformContext) transformMergeMatch(n tnode) ast.MergeIntoAction { action := ast.MergeIntoAction{} action.SetSpan(n.span()) diff --git a/parser/transform_expr.go b/parser/transform_expr.go index e13b045..27a2971 100644 --- a/parser/transform_expr.go +++ b/parser/transform_expr.go @@ -845,7 +845,16 @@ func (tc *transformContext) transformBase(n tnode) ast.Expr { bare := singleAlt.name() == "ColumnReference" if indirections, ok := n.child(1).opt(); ok { items := indirections.sole().repeat() + prevWasCast := false for i, ind := range items { + // upstream rejects an operator-class indirection (subscript / + // slice) directly after a `::` cast + _, kind := ind.sole().choice() + if kind.name() == "SliceExpression" && prevWasCast { + raise("Subscript/slice cannot be applied directly after a cast operator " + + "(e.g. x::TYPE[1:3] is not allowed). Wrap the cast in parentheses: (x::TYPE)[1:3]") + } + prevWasCast = kind.name() == "CastOperator" expr, bare = tc.transformIndirection(expr, ind, n.span().Start, bare, i == len(items)-1) } } diff --git a/parser/transform_func.go b/parser/transform_func.go index 963d46c..bf7edc8 100644 --- a/parser/transform_func.go +++ b/parser/transform_func.go @@ -56,16 +56,31 @@ func (tc *transformContext) transformFunction(n tnode) ast.Expr { parts := tc.transformFunctionArgumentList(n.child(1).sole().parens()) starCallRewrite(&parts) if wg, ok := n.child(2).opt(); ok { - // WITHIN GROUP (ORDER BY ...): the ordering becomes the - // function's ORDER BY, and the percentile functions are renamed - // to their quantile implementations + // WITHIN GROUP (ORDER BY ...): the ordering replaces the + // function's ORDER BY, and only the ordered-set aggregates are + // accepted (renamed to their quantile implementations) orderBy := wg.child(2).parens() - parts.orderBys = append(parts.orderBys, tc.transformOrderByClause(orderBy)...) + parts.orderBys = tc.transformOrderByClause(orderBy) + if len(parts.orderBys) != 1 { + raise("Cannot use multiple ORDER BY clauses with WITHIN GROUP") + } switch name { case "percentile_cont": + if len(parts.args) != 1 { + raise("Wrong number of arguments for PERCENTILE_CONT") + } name = "quantile_cont" case "percentile_disc": + if len(parts.args) != 1 { + raise("Wrong number of arguments for PERCENTILE_DISC") + } name = "quantile_disc" + case "mode": + if len(parts.args) != 0 { + raise("Wrong number of arguments for MODE") + } + default: + raise("Unknown ordered aggregate \"%s\".", name) } } var filter ast.Expr @@ -77,6 +92,9 @@ func (tc *transformContext) transformFunction(n tnode) ast.Expr { _, exportState := n.child(4).opt() if over, ok := n.child(5).opt(); ok { + if exportState { + raise("EXPORT_STATE is not supported for window functions!") + } return tc.transformOver(over, catalog, schema, name, parts, filter, n.span()) } if parts.hasNullsOpt { @@ -236,6 +254,9 @@ func (tc *transformContext) transformFunctionArgument(n tnode) ast.FunctionArgum // transformOver builds the WindowExpression for a call with an OVER // clause. func (tc *transformContext) transformOver(n tnode, catalog, schema, name string, parts functionParts, filter ast.Expr, sp ast.Span) ast.Expr { + if tc.inWindowDefinition { + raise("window functions are not allowed in window definitions") + } w := &ast.WindowExpression{ Catalog: catalog, Schema: schema, @@ -267,11 +288,17 @@ func (tc *transformContext) transformOver(n tnode, catalog, schema, name string, _, frame := n.child(1).sole().choice() switch frame.name() { case "ParensIdentifier": - tc.applyNamedWindow(w, identifierText(frame.sole().parens())) + name := identifierText(frame.sole().parens()) + tc.applyNamedWindow(w, name) + if windowHasFrameClause(w) { + raise("cannot copy window \"%s\" because it has a frame clause", name) + } case "IdentifierWindowFrame": tc.applyNamedWindow(w, identifierText(frame.sole())) case "WindowFrameDefinition": + tc.inWindowDefinition = true tc.transformWindowFrameDefinition(w, frame) + tc.inWindowDefinition = false default: shapeError(frame, "unknown window frame") } @@ -302,25 +329,77 @@ func copyWindowSpec(dst, src *ast.WindowExpression) { func (tc *transformContext) transformWindowFrameDefinition(w *ast.WindowExpression, n tnode) { _, alt := n.sole().choice() var contents tnode + var baseName tnode + hasBase := false switch alt.name() { case "WindowFrameNameContentsParens": inner := alt.sole().parens() // WindowFrameNameContents <- BaseWindowName? WindowFrameContents - if baseName, ok := inner.child(0).opt(); ok { - tc.applyNamedWindow(w, identifierText(baseName)) - } + baseName, hasBase = inner.child(0).opt() contents = inner.child(1) case "WindowFrameContentsParens": contents = alt.sole().parens() default: shapeError(alt, "unknown window frame definition") } - // WindowFrameContents <- WindowPartition? OrderByClause? FrameClause? + if !hasBase { + tc.fillWindowContents(w, contents) + return + } + // OVER (base ...) extends a named window; upstream forbids copying a + // window with a frame clause and overriding its ORDER/PARTITION BY + name := identifierText(baseName) + switch strings.ToLower(name) { + case "partition", "range", "rows", "groups": + raise("Invalid window name \"%s\"", name) + } + tc.applyNamedWindow(w, name) + if windowHasFrameClause(w) { + raise("cannot copy window \"%s\" because it has a frame clause", name) + } + over := &ast.WindowExpression{ + FrameStart: ast.WindowUnboundedPreceding, FrameEnd: ast.WindowCurrentRowRange, + ExcludeClause: ast.WindowExcludeNoOther, + } + tc.fillWindowContents(over, contents) + w.FrameStart, w.FrameEnd = over.FrameStart, over.FrameEnd + w.StartExpr, w.EndExpr = over.StartExpr, over.EndExpr + w.ExcludeClause = over.ExcludeClause + if len(w.Orders) > 0 && len(over.Orders) > 0 { + raise("Cannot override ORDER BY clause of window \"%s\"", name) + } + if len(w.Orders) == 0 { + w.Orders = over.Orders + } + if len(w.Partitions) > 0 && len(over.Partitions) > 0 { + raise("Cannot override PARTITION BY clause of window \"%s\"", name) + } + if len(w.Partitions) == 0 { + w.Partitions = over.Partitions + } +} + +// windowHasFrameClause ports IsWindowFrameDefault's negation plus the +// explicit bound expressions. +func windowHasFrameClause(w *ast.WindowExpression) bool { + return w.StartExpr != nil || w.EndExpr != nil || + w.FrameStart != ast.WindowUnboundedPreceding || w.FrameEnd != ast.WindowCurrentRowRange +} + +// fillWindowContents maps WindowFrameContents +// (WindowPartition? OrderByClause? FrameClause?). +func (tc *transformContext) fillWindowContents(w *ast.WindowExpression, contents tnode) { if part, ok := contents.child(0).opt(); ok { w.Partitions = tc.transformExpressionList(part.child(2)) } if ob, ok := contents.child(1).opt(); ok { - w.Orders = append(w.Orders, tc.transformOrderByClause(ob)...) + orders := tc.transformOrderByClause(ob) + for _, o := range orders { + if star, isStar := o.Expression.(*ast.StarExpression); isStar && star.Expr == nil { + raise("Cannot ORDER BY ALL in a window expression") + } + } + w.Orders = append(w.Orders, orders...) } if fc, ok := contents.child(2).opt(); ok { tc.transformFrameClause(w, fc) @@ -354,11 +433,17 @@ func (tc *transformContext) transformFrameClause(w *ast.WindowExpression, n tnod case "BetweenFrameExtent": start, startExpr := tc.transformFrameBound(extent.child(1), kind, true) end, endExpr := tc.transformFrameBound(extent.child(3), kind, false) + if end == ast.WindowUnboundedPreceding { + raise("Frame end cannot be UNBOUNDED PRECEDING") + } w.FrameStart, w.StartExpr = start, startExpr w.FrameEnd, w.EndExpr = end, endExpr default: shapeError(extent, "unknown frame extent") } + if w.FrameStart == ast.WindowUnboundedFollowing { + raise("Frame start cannot be UNBOUNDED FOLLOWING") + } if excl, ok := n.child(2).opt(); ok { _, e := excl.child(1).sole().choice() switch e.name() { diff --git a/parser/transform_misc.go b/parser/transform_misc.go new file mode 100644 index 0000000..5eae438 --- /dev/null +++ b/parser/transform_misc.go @@ -0,0 +1,849 @@ +// transform_misc.go: the milestone-5 statement transformers — SET/RESET, +// PRAGMA, CALL, USE, CHECKPOINT, transactions, VACUUM/ANALYZE, +// EXPORT/IMPORT, prepared statements, EXPLAIN, ATTACH/DETACH/CONNECT, +// COMMENT ON and the extension statements. Port of upstream's +// transformer/transform_set.cpp, transform_pragma.cpp, transform_call.cpp, +// transform_use.cpp, transform_checkpoint.cpp, transform_transaction.cpp, +// transform_vacuum.cpp, transform_analyze.cpp, transform_export.cpp, +// transform_prepare.cpp, transform_execute.cpp, transform_explain.cpp, +// transform_attach.cpp, transform_detach.cpp, transform_connect.cpp, +// transform_comment.cpp, transform_deallocate.cpp, transform_load.cpp and +// transform_external_resource.cpp. +// +// Upstream raises two flavors of error from these transformers: +// ParserException (a parse-time reject, reproduced here with raise) and +// NotImplementedException/InvalidInputException/BinderException (post-parse +// classification for the corpus oracle, so darkwing records the construct +// and accepts). Each site is annotated. +package parser + +import ( + "strconv" + "strings" + + "github.com/sqlc-dev/darkwing/ast" +) + +func init() { + register("SetStatement", func(tc *transformContext, n tnode) ast.Stmt { + return tc.transformSet(n) + }) + register("ResetStatement", func(tc *transformContext, n tnode) ast.Stmt { + return tc.transformReset(n) + }) + register("PragmaStatement", func(tc *transformContext, n tnode) ast.Stmt { + return tc.transformPragma(n) + }) + register("CallStatement", func(tc *transformContext, n tnode) ast.Stmt { + return tc.transformCall(n) + }) + register("UseStatement", func(tc *transformContext, n tnode) ast.Stmt { + return tc.transformUse(n) + }) + register("CheckpointStatement", func(tc *transformContext, n tnode) ast.Stmt { + return tc.transformCheckpoint(n) + }) + register("TransactionStatement", func(tc *transformContext, n tnode) ast.Stmt { + return tc.transformTransaction(n) + }) + register("VacuumStatement", func(tc *transformContext, n tnode) ast.Stmt { + return tc.transformVacuum(n) + }) + register("AnalyzeStatement", func(tc *transformContext, n tnode) ast.Stmt { + return tc.transformAnalyze(n) + }) + register("ExportStatement", func(tc *transformContext, n tnode) ast.Stmt { + return tc.transformExport(n) + }) + register("ImportStatement", func(tc *transformContext, n tnode) ast.Stmt { + stmt := &ast.ImportStatement{Path: identifierText(n.child(2))} + stmt.SetSpan(n.span()) + return stmt + }) + register("DeallocateStatement", func(tc *transformContext, n tnode) ast.Stmt { + // 'DEALLOCATE' DeallocatePrepare? Identifier + stmt := &ast.DeallocateStatement{Name: identifierText(n.child(2))} + stmt.SetSpan(n.span()) + return stmt + }) + register("PrepareStatement", func(tc *transformContext, n tnode) ast.Stmt { + return tc.transformPrepare(n) + }) + register("ExecuteStatement", func(tc *transformContext, n tnode) ast.Stmt { + return tc.transformExecute(n) + }) + register("ExplainStatement", func(tc *transformContext, n tnode) ast.Stmt { + return tc.transformExplain(n) + }) + register("AttachStatement", func(tc *transformContext, n tnode) ast.Stmt { + return tc.transformAttach(n) + }) + register("DetachStatement", func(tc *transformContext, n tnode) ast.Stmt { + // 'DETACH' Database? IfExists? CatalogName + stmt := &ast.DetachStatement{Name: identifierText(n.child(3))} + _, stmt.IfExists = n.child(2).opt() + stmt.SetSpan(n.span()) + return stmt + }) + register("ConnectStatement", func(tc *transformContext, n tnode) ast.Stmt { + return tc.transformConnect(n) + }) + register("DisconnectStatement", func(tc *transformContext, n tnode) ast.Stmt { + stmt := &ast.DisconnectStatement{} + stmt.SetSpan(n.span()) + return stmt + }) + register("CommentStatement", func(tc *transformContext, n tnode) ast.Stmt { + return tc.transformComment(n) + }) + register("LoadStatement", func(tc *transformContext, n tnode) ast.Stmt { + return tc.transformLoad(n) + }) + register("InstallStatement", func(tc *transformContext, n tnode) ast.Stmt { + return tc.transformInstall(n) + }) + register("UpdateExtensionsStatement", func(tc *transformContext, n tnode) ast.Stmt { + // 'UPDATE' 'EXTENSIONS' Parens(List(Identifier))? + stmt := &ast.UpdateExtensionsStatement{} + stmt.SetSpan(n.span()) + if list, ok := n.child(2).opt(); ok { + stmt.Extensions = identifierList(list.parens()) + } + return stmt + }) + register("ExternalResourceStatement", func(tc *transformContext, n tnode) ast.Stmt { + return tc.transformExternalResource(n) + }) +} + +// ---- SET / RESET ------------------------------------------------------- + +// settingTarget is the (name, scope) pair of SetVariableOrSetting. +// SetVariableOrSetting <- SetVariable / SetSetting +func (tc *transformContext) transformSettingTarget(n tnode) (string, ast.SetScope) { + _, alt := n.sole().choice() + switch alt.name() { + case "SetVariable": + // VariableScope Identifier + return identifierText(alt.child(1)), ast.SetScopeVariable + case "SetSetting": + // SettingScope? SettingName + scope := ast.SetScopeAutomatic + if sc, ok := alt.child(0).opt(); ok { + _, kind := sc.sole().choice() + switch kind.name() { + case "LocalScope": + scope = ast.SetScopeLocal + case "SessionScope": + scope = ast.SetScopeSession + case "GlobalScope": + scope = ast.SetScopeGlobal + } + } + return identifierText(alt.child(1)), scope + } + shapeError(alt, "unknown setting target") + return "", ast.SetScopeAutomatic +} + +// SetStatement <- 'SET' SetAssignmentOrTimeZone +func (tc *transformContext) transformSet(n tnode) ast.Stmt { + sp := n.span() + _, alt := n.child(1).sole().choice() + switch alt.name() { + case "SetSchema": + // 'SCHEMA' StringLiteral + stmt := &ast.SetStatement{Name: "schema", Scope: ast.SetScopeAutomatic} + stmt.Value = constExpr(invalidSpan, varcharValue(identifierText(alt.child(1)))) + stmt.SetSpan(sp) + return stmt + case "StandardAssignment": + // SetVariableOrSetting SetAssignment + name, scope := tc.transformSettingTarget(alt.child(0)) + // SET LOCAL raises NotImplemented upstream (post-parse for the + // corpus); darkwing keeps the scope and accepts. + // SetAssignment <- VariableAssign VariableList + values := tc.transformExpressionList(alt.child(1).child(1)) + if len(values) > 1 { + raise("SET can only contain a single value") + } + value := values[0] + switch v := value.(type) { + case *ast.ColumnRefExpression: + // a bare identifier value is its name as a string + value = constExpr(invalidSpan, varcharValue(v.ColumnNames[len(v.ColumnNames)-1])) + case *ast.DefaultExpression: + stmt := &ast.ResetStatement{Name: name, Scope: scope} + stmt.SetSpan(sp) + return stmt + } + stmt := &ast.SetStatement{Name: name, Scope: scope, Value: value} + stmt.SetSpan(sp) + return stmt + case "SetTimeZone": + // 'TIME' 'ZONE' ZoneValue + value := tc.transformZoneValue(alt.child(2)) + if _, isDefault := value.(*ast.DefaultExpression); isDefault { + stmt := &ast.ResetStatement{Name: "timezone", Scope: ast.SetScopeAutomatic} + stmt.SetSpan(sp) + return stmt + } + stmt := &ast.SetStatement{Name: "timezone", Scope: ast.SetScopeAutomatic, Value: value} + stmt.SetSpan(sp) + return stmt + } + shapeError(alt, "unknown SET form") + return nil +} + +// ZoneValue <- ZoneIntervalWithPrecision / ZoneIntervalWithInterval / +// ZoneLocal / ZoneDefault / ZoneStringLiteral / ZoneIdentifier / NumberLiteral +func (tc *transformContext) transformZoneValue(n tnode) ast.Expr { + _, alt := n.sole().choice() + switch alt.name() { + case "ZoneLocal", "ZoneDefault": + d := &ast.DefaultExpression{} + d.SetSpan(invalidSpan) + return d + case "ZoneStringLiteral", "ZoneIdentifier": + return constExpr(invalidSpan, varcharValue(identifierText(alt.sole()))) + case "ZoneIntervalWithInterval": + // 'INTERVAL' StringLiteral Interval? -> CAST(str AS INTERVAL) + return intervalCast(identifierText(alt.child(1))) + case "ZoneIntervalWithPrecision": + // 'INTERVAL' Parens(NumberLiteral) StringLiteral + return intervalCast(identifierText(alt.child(2))) + } + // the last alternative is a bare NumberLiteral token + return numberConstant(invalidSpan, alt.number()) +} + +func intervalCast(s string) ast.Expr { + cast := &ast.CastExpression{ + Child: constExpr(invalidSpan, varcharValue(s)), + ResolvedType: "INTERVAL", + } + cast.SetSpan(invalidSpan) + return cast +} + +// ResetStatement <- 'RESET' SetVariableOrSetting. RESET LOCAL raises +// NotImplemented upstream (post-parse); darkwing keeps the scope. +func (tc *transformContext) transformReset(n tnode) ast.Stmt { + name, scope := tc.transformSettingTarget(n.child(1)) + stmt := &ast.ResetStatement{Name: name, Scope: scope} + stmt.SetSpan(n.span()) + return stmt +} + +// ---- PRAGMA ------------------------------------------------------------ + +// sqlitePragmas are parsed as pragma calls even in assignment form, for +// SQLite compatibility (upstream's sqlite_compat_pragmas). +var sqlitePragmas = map[string]bool{"table_info": true} + +// PragmaStatement <- 'PRAGMA' PragmaAssignOrFunction +func (tc *transformContext) transformPragma(n tnode) ast.Stmt { + sp := n.span() + _, alt := n.child(1).sole().choice() + switch alt.name() { + case "PragmaAssign": + // SettingName '=' VariableList + name := identifierText(alt.child(0)) + values := tc.transformExpressionList(alt.child(2)) + if len(values) != 1 { + raise("PRAGMA statement with assignment should contain exactly one parameter") + } + value := pragmaParamValue(values[0]) + if sqlitePragmas[strings.ToLower(name)] { + stmt := &ast.PragmaStatement{Name: name, Parameters: []ast.Expr{value}} + stmt.SetSpan(sp) + return stmt + } + // other assignment pragmas are SET statements upstream + stmt := &ast.SetStatement{Name: name, Scope: ast.SetScopeAutomatic, Value: value} + stmt.SetSpan(sp) + return stmt + case "PragmaFunction": + // PragmaName PragmaParameters? + stmt := &ast.PragmaStatement{Name: identifierText(alt.child(0))} + stmt.SetSpan(sp) + if params, ok := alt.child(1).opt(); ok { + for _, e := range tc.transformExpressionList(params.parens()) { + if cmp, isCmp := e.(*ast.ComparisonExpression); isCmp && cmp.Type == ast.CompareEqual { + ref, isRef := cmp.Left.(*ast.ColumnRefExpression) + if !isRef { + raise("Named parameter requires a column reference on the LHS") + } + name := ref.ColumnNames[len(ref.ColumnNames)-1] + stmt.NamedParameters = append(stmt.NamedParameters, ast.NamedValue{Name: name, Expr: cmp.Right}) + continue + } + stmt.Parameters = append(stmt.Parameters, pragmaParamValue(e)) + } + } + return stmt + } + shapeError(alt, "unknown pragma form") + return nil +} + +// pragmaParamValue folds bare identifiers into string constants the way +// upstream does for PRAGMA parameters. +func pragmaParamValue(e ast.Expr) ast.Expr { + ref, ok := e.(*ast.ColumnRefExpression) + if !ok { + return e + } + if len(ref.ColumnNames) == 1 { + return constExpr(invalidSpan, varcharValue(ref.ColumnNames[0])) + } + return constExpr(invalidSpan, varcharValue(strings.Join(ref.ColumnNames, "."))) +} + +// ---- CALL / USE / CHECKPOINT ------------------------------------------ + +// CallStatement <- 'CALL' QualifiedTableFunction TableFunctionArguments +func (tc *transformContext) transformCall(n tnode) ast.Stmt { + stmt := &ast.CallStatement{Function: tc.qualifiedFunctionExpr(n.child(1), n.child(2))} + stmt.SetSpan(n.span()) + return stmt +} + +// UseStatement <- 'USE' UseTarget +func (tc *transformContext) transformUse(n tnode) ast.Stmt { + stmt := &ast.UseStatement{} + stmt.SetSpan(n.span()) + _, alt := n.child(1).sole().choice() + switch alt.name() { + case "SchemaNameAsUseTarget", "CatalogNameAsUseTarget": + stmt.Name = identifierText(alt.sole()) + case "UseTargetCatalogSchema": + // CatalogName '.' ReservedSchemaName DotIdentifier* + if len(alt.child(3).repeat()) > 0 { + raise("Expected \"USE database\" or \"USE database.schema\"") + } + stmt.Catalog = identifierText(alt.child(0)) + stmt.Name = identifierText(alt.child(2)) + default: + shapeError(alt, "unknown USE target") + } + return stmt +} + +// CheckpointStatement <- CheckpointForce? 'CHECKPOINT' CatalogName? +// Upstream desugars into CALL [force_]checkpoint(catalog?). +func (tc *transformContext) transformCheckpoint(n tnode) ast.Stmt { + stmt := &ast.CheckpointStatement{} + stmt.SetSpan(n.span()) + _, stmt.Force = n.child(0).opt() + if cat, ok := n.child(2).opt(); ok { + stmt.Catalog = identifierText(cat) + } + return stmt +} + +// ---- transactions ------------------------------------------------------ + +// TransactionStatement <- BeginTransaction / RollbackTransaction / CommitTransaction +func (tc *transformContext) transformTransaction(n tnode) ast.Stmt { + stmt := &ast.TransactionStatement{} + stmt.SetSpan(n.span()) + _, alt := n.sole().choice() + switch alt.name() { + case "BeginTransaction": + // StartOrBegin Transaction? ReadOrWrite? + stmt.Kind = ast.TransactionBegin + if rw, ok := alt.child(2).opt(); ok { + // ReadOrWrite <- 'READ' ReadOnlyOrReadWrite + _, kind := rw.child(1).sole().choice() + if kind.name() == "ReadOnly" { + stmt.Modifier = ast.TransactionReadOnly + } else { + stmt.Modifier = ast.TransactionReadWrite + } + } + case "CommitTransaction": + stmt.Kind = ast.TransactionCommit + case "RollbackTransaction": + stmt.Kind = ast.TransactionRollback + default: + shapeError(alt, "unknown transaction form") + } + return stmt +} + +// ---- VACUUM / ANALYZE -------------------------------------------------- + +// analyzeTarget maps AnalyzeTarget (BaseTableName NameList?). +func (tc *transformContext) transformAnalyzeTarget(n tnode) (ast.TableRef, []string) { + ref := &ast.BaseTableRef{} + ref.SetSpan(n.child(0).span()) + tc.fillBaseTableName(ref, n.child(0)) + var cols []string + if list, ok := n.child(1).opt(); ok { + cols = identifierList(list.parens()) + } + return ref, cols +} + +// VacuumStatement <- 'VACUUM' VacuumOptions? AnalyzeTarget? +// FULL/FREEZE/VERBOSE and unknown parenthesized options raise +// NotImplemented upstream (post-parse); darkwing records the raw options. +func (tc *transformContext) transformVacuum(n tnode) ast.Stmt { + stmt := &ast.VacuumStatement{} + stmt.SetSpan(n.span()) + if opts, ok := n.child(1).opt(); ok { + stmt.Vacuum = true + _, alt := opts.sole().choice() + switch alt.name() { + case "VacuumParensOptions": + for _, o := range alt.sole().parens().listElems() { + _, opt := o.sole().choice() + var name string + switch opt.name() { + case "OptAnalyze": + name = "analyze" + case "OptFull": + name = "full" + case "OptFreeze": + name = "freeze" + case "OptVerbose": + name = "verbose" + default: + name = identifierText(opt) + } + stmt.Options = append(stmt.Options, name) + if strings.EqualFold(name, "analyze") { + stmt.Analyze = true + } + } + case "VacuumLegacyOptions": + // OptFull? OptFreeze? OptVerbose? OptAnalyze? + for i, name := range []string{"full", "freeze", "verbose", "analyze"} { + if _, ok := alt.child(i).opt(); ok { + stmt.Options = append(stmt.Options, name) + if name == "analyze" { + stmt.Analyze = true + } + } + } + default: + shapeError(alt, "unknown vacuum options") + } + } + if target, ok := n.child(2).opt(); ok { + stmt.Table, stmt.Columns = tc.transformAnalyzeTarget(target) + } + return stmt +} + +// AnalyzeStatement <- AnalyzeKeyword AnalyzeVerbose? AnalyzeTarget? +// Upstream desugars into VACUUM with the analyze option; ANALYZE VERBOSE +// raises NotImplemented upstream (post-parse). +func (tc *transformContext) transformAnalyze(n tnode) ast.Stmt { + stmt := &ast.AnalyzeStatement{} + stmt.SetSpan(n.span()) + _, stmt.Verbose = n.child(1).opt() + if target, ok := n.child(2).opt(); ok { + stmt.Table, stmt.Columns = tc.transformAnalyzeTarget(target) + } + return stmt +} + +// ---- EXPORT ------------------------------------------------------------ + +// ExportStatement <- 'EXPORT' 'DATABASE' ExportSource? StringLiteral GenericCopyOptionList? +func (tc *transformContext) transformExport(n tnode) ast.Stmt { + stmt := &ast.ExportStatement{Format: "csv"} + stmt.SetSpan(n.span()) + if src, ok := n.child(2).opt(); ok { + // ExportSource <- CatalogName 'TO' + stmt.Database = identifierText(src.child(0)) + } + stmt.Path = identifierText(n.child(3)) + if opts, ok := n.child(4).opt(); ok { + for _, option := range tc.transformGenericOptionList(opts) { + if strings.EqualFold(option.Name, "format") { + if option.Expr != nil { + raise("Unsupported parameter type for FORMAT: expected e.g. FORMAT 'csv', 'parquet'") + } + if len(option.Values) == 0 { + raise("FORMAT requires a parameter, e.g. FORMAT 'csv' or FORMAT 'parquet'") + } + stmt.Format = option.Values[0].Str + continue + } + stmt.Options = append(stmt.Options, option) + } + } + return stmt +} + +// ---- PREPARE / EXECUTE ------------------------------------------------- + +// statementTypeName names a statement the way upstream's StatementType +// spellings do, for transformer error messages. +func statementTypeName(stmt ast.Stmt) string { + switch stmt.(type) { + case *ast.SelectStatement: + return "SELECT_STATEMENT" + case *ast.InsertStatement: + return "INSERT_STATEMENT" + case *ast.UpdateStatement: + return "UPDATE_STATEMENT" + case *ast.DeleteStatement, *ast.TruncateStatement: + return "DELETE_STATEMENT" + case *ast.CopyStatement: + return "COPY_STATEMENT" + case *ast.CreateStatement: + return "CREATE_STATEMENT" + case *ast.DropStatement, *ast.DeallocateStatement: + return "DROP_STATEMENT" + case *ast.AlterStatement, *ast.CommentOnStatement: + return "ALTER_STATEMENT" + case *ast.MergeIntoStatement: + return "MERGE_INTO_STATEMENT" + case *ast.TransactionStatement: + return "TRANSACTION_STATEMENT" + case *ast.PragmaStatement, *ast.ImportStatement: + return "PRAGMA_STATEMENT" + case *ast.SetStatement, *ast.ResetStatement, *ast.UseStatement: + return "SET_STATEMENT" + case *ast.CallStatement, *ast.CheckpointStatement: + return "CALL_STATEMENT" + case *ast.VacuumStatement, *ast.AnalyzeStatement: + return "VACUUM_STATEMENT" + case *ast.ExplainStatement: + return "EXPLAIN_STATEMENT" + case *ast.PrepareStatement: + return "PREPARE_STATEMENT" + case *ast.ExecuteStatement: + return "EXECUTE_STATEMENT" + case *ast.ExportStatement: + return "EXPORT_STATEMENT" + case *ast.AttachStatement: + return "ATTACH_STATEMENT" + case *ast.DetachStatement: + return "DETACH_STATEMENT" + case *ast.LoadStatement: + return "LOAD_STATEMENT" + case *ast.CopyDatabaseStatement: + return "COPY_DATABASE_STATEMENT" + case *ast.UpdateExtensionsStatement: + return "UPDATE_EXTENSIONS_STATEMENT" + default: + return "INVALID_STATEMENT" + } +} + +// preparableStatement mirrors upstream's IsPrepareableStatement: +// SELECT/INSERT/UPDATE/DELETE/COPY (TRUNCATE is a delete upstream). +func preparableStatement(stmt ast.Stmt) bool { + switch stmt.(type) { + case *ast.SelectStatement, *ast.InsertStatement, *ast.UpdateStatement, + *ast.DeleteStatement, *ast.TruncateStatement, *ast.CopyStatement: + return true + } + return false +} + +// PrepareStatement <- 'PREPARE' Identifier TypeList? 'AS' Statement +// A TypeList raises NotImplemented upstream (post-parse); darkwing +// ignores it. The inner statement's parameters belong to EXECUTE, so the +// collected parameter state is cleared, mirroring upstream's +// ClearParameters. +func (tc *transformContext) transformPrepare(n tnode) ast.Stmt { + stmt := &ast.PrepareStatement{Name: identifierText(n.child(1))} + stmt.SetSpan(n.span()) + inner := tc.transformStatement(n.child(4)) + if !preparableStatement(inner) { + raise("%s is not a preparable statement", statementTypeName(inner)) + } + stmt.Statement = inner + tc.paramCount = 0 + tc.paramSeen = map[string]int{} + tc.paramOrder = nil + tc.hasNamed, tc.hasPosition = false, false + return stmt +} + +// ExecuteStatement <- 'EXECUTE' Identifier TableFunctionArguments? +// Non-scalar and mixed named/positional arguments raise +// InvalidInput/NotImplemented upstream (post-parse); darkwing records +// the arguments as written. +func (tc *transformContext) transformExecute(n tnode) ast.Stmt { + stmt := &ast.ExecuteStatement{Name: identifierText(n.child(1))} + stmt.SetSpan(n.span()) + args, ok := n.child(2).opt() + if !ok { + return stmt + } + positional := 0 + if list, hasArgs := args.sole().parens().opt(); hasArgs { + for _, a := range list.listElems() { + arg := tc.transformFunctionArgument(a) + name := arg.Name + if name == "" { + positional++ + name = strconv.Itoa(positional) + } + arg.Expr.SetAlias("") + stmt.Values = append(stmt.Values, ast.NamedValue{Name: name, Expr: arg.Expr}) + } + } + return stmt +} + +// ---- EXPLAIN ----------------------------------------------------------- + +// ExplainStatement <- 'EXPLAIN' AnalyzeKeyword? ExplainOptionList? ExplainableStatements +// Unknown options and malformed FORMAT arguments raise +// NotImplemented/InvalidInput upstream (post-parse); darkwing records +// what it recognizes and accepts. +func (tc *transformContext) transformExplain(n tnode) ast.Stmt { + stmt := &ast.ExplainStatement{Type: ast.ExplainStandard} + stmt.SetSpan(n.span()) + if _, ok := n.child(1).opt(); ok { + stmt.Type = ast.ExplainAnalyze + } + if opts, ok := n.child(2).opt(); ok { + // ExplainOptionList <- Parens(List(ExplainOption)) + for _, o := range opts.sole().parens().listElems() { + // ExplainOption <- ExplainOptionName Expression? + name := strings.ToLower(identifierText(o.child(0))) + option := ast.GenericOption{Name: name} + if e, hasExpr := o.child(1).opt(); hasExpr { + setGenericOptionExpr(&option, tc.transformExpression(e)) + } + switch name { + case "format": + if len(option.Values) > 0 && option.Values[0].Kind == ast.ValueString { + stmt.Format = strings.ToLower(option.Values[0].Str) + } + case "analyze": + stmt.Type = ast.ExplainAnalyze + } + } + } + // ExplainableStatements is a choice of statement rules (with + // ExplainSelectStatement wrapping SelectStatementInternal). + _, inner := n.child(3).sole().choice() + if inner.name() == "ExplainSelectStatement" { + stmt.Statement = tc.transformSelectInternalStatement(inner.sole()) + } else if fn, ok := statementTransforms[inner.name()]; ok { + stmt.Statement = fn(tc, inner) + } else { + shapeError(inner, "unknown explainable statement") + } + return stmt +} + +// ---- ATTACH / CONNECT -------------------------------------------------- + +// checkSingleValuedOptions ports SplitGenericOptions' parse-time check: +// an option folded to constants can carry at most one argument. +func checkSingleValuedOptions(opts []ast.GenericOption) { + for _, o := range opts { + if o.Expr == nil && len(o.Values) > 1 { + raise("Option %s can only have one argument", o.Name) + } + } +} + +// AttachStatement <- 'ATTACH' OrReplace? IfNotExists? Database? DatabasePath AttachAlias? AttachOptions? +func (tc *transformContext) transformAttach(n tnode) ast.Stmt { + stmt := &ast.AttachStatement{OnConflict: ast.CreateError} + stmt.SetSpan(n.span()) + _, orReplace := n.child(1).opt() + _, ifNotExists := n.child(2).opt() + if orReplace && ifNotExists { + raise("Cannot specify both OR REPLACE and IF NOT EXISTS at the same time") + } + if orReplace { + stmt.OnConflict = ast.CreateReplace + } else if ifNotExists { + stmt.OnConflict = ast.CreateIgnore + } + stmt.Path = tc.transformExpression(n.child(4).sole()) + if alias, ok := n.child(5).opt(); ok { + // AttachAlias <- 'AS' ColId + stmt.Alias = identifierText(alias.child(1)) + } + if opts, ok := n.child(6).opt(); ok { + stmt.Options = tc.transformGenericOptionList(opts.sole()) + checkSingleValuedOptions(stmt.Options) + } + return stmt +} + +// ConnectStatement <- 'CONNECT' SessionTarget? +func (tc *transformContext) transformConnect(n tnode) ast.Stmt { + stmt := &ast.ConnectStatement{} + stmt.SetSpan(n.span()) + target, ok := n.child(1).opt() + if !ok { + return stmt + } + _, alt := target.sole().choice() + switch alt.name() { + case "LocalSessionTarget": + stmt.Local = true + case "StringSessionTarget": + // StringLiteral GenericCopyOptionList? + stmt.Name = identifierText(alt.child(0)) + stmt.NameIsString = true + if opts, hasOpts := alt.child(1).opt(); hasOpts { + stmt.Options = tc.transformGenericOptionList(opts) + checkSingleValuedOptions(stmt.Options) + } + case "CatalogSessionTarget": + stmt.Name = identifierText(alt.sole()) + default: + shapeError(alt, "unknown session target") + } + return stmt +} + +// ---- COMMENT ON -------------------------------------------------------- + +// commentOnTypes maps the CommentOnType alternative rules. +var commentOnTypes = map[string]ast.CommentOnType{ + "CommentTable": ast.CommentOnTable, + "CommentSequence": ast.CommentOnSequence, + "CommentFunction": ast.CommentOnMacro, // FUNCTION and MACRO share the macro entry + "CommentMacroTable": ast.CommentOnMacroTable, + "CommentMacro": ast.CommentOnMacro, + "CommentView": ast.CommentOnView, + "CommentDatabase": ast.CommentOnDatabase, + "CommentIndex": ast.CommentOnIndex, + "CommentSchema": ast.CommentOnSchema, + "CommentType": ast.CommentOnTypeEntry, + "CommentColumn": ast.CommentOnColumn, +} + +// CommentStatement <- 'COMMENT' 'ON' CommentOnType DottedIdentifier 'IS' CommentValue +// Upstream desugars into ALTER with a comment info; COMMENT ON +// DATABASE/SCHEMA raise NotImplemented upstream (post-parse). +func (tc *transformContext) transformComment(n tnode) ast.Stmt { + stmt := &ast.CommentOnStatement{} + stmt.SetSpan(n.span()) + _, kind := n.child(2).sole().choice() + onType, known := commentOnTypes[kind.name()] + if !known { + shapeError(kind, "unknown comment target") + } + stmt.OnType = onType + parts := dottedIdentifier(n.child(3)) + if onType == ast.CommentOnColumn { + stmt.Column = parts[len(parts)-1] + parts = parts[:len(parts)-1] + if len(parts) == 0 { + raise("Invalid column reference: '%s'", stmt.Column) + } + } + stmt.Catalog, stmt.Schema, stmt.Name = qualifiedNameFromParts(parts) + // CommentValue <- NullLiteral / StringLiteralValue + _, value := n.child(5).sole().choice() + if value.name() == "NullLiteral" { + stmt.Value = constExpr(invalidSpan, ast.Value{Type: ast.LogicalType{ID: "NULL"}, IsNull: true, Kind: ast.ValueNull}) + } else { + stmt.Value = constExpr(invalidSpan, varcharValue(identifierText(value))) + } + return stmt +} + +// qualifiedNameFromParts splits a dotted path into catalog/schema/name, +// mirroring upstream's QualifiedName::StringToQualifiedName. +func qualifiedNameFromParts(parts []string) (catalog, schema, name string) { + switch len(parts) { + case 1: + return "", "", parts[0] + case 2: + return "", parts[0], parts[1] + case 3: + return parts[0], parts[1], parts[2] + } + raise("Too many qualifications for name \"%s\"", strings.Join(parts, ".")) + return "", "", "" +} + +// ---- extensions -------------------------------------------------------- + +// LoadStatement <- 'LOAD' ColIdOrString ExtensionAlias? +func (tc *transformContext) transformLoad(n tnode) ast.Stmt { + stmt := &ast.LoadStatement{Kind: ast.LoadTypeLoad, Name: identifierText(n.child(1))} + stmt.SetSpan(n.span()) + if alias, ok := n.child(2).opt(); ok { + // ExtensionAlias <- 'AS' Identifier + stmt.Kind = ast.LoadTypeLoadAs + stmt.Alias = identifierText(alias.child(1)) + } + return stmt +} + +// InstallStatement <- 'FORCE'? 'INSTALL' IdentifierOrStringLiteral FromSource? VersionNumber? +func (tc *transformContext) transformInstall(n tnode) ast.Stmt { + stmt := &ast.LoadStatement{Kind: ast.LoadTypeInstall} + stmt.SetSpan(n.span()) + if _, force := n.child(0).opt(); force { + stmt.Kind = ast.LoadTypeForceInstall + } + stmt.Name = identifierText(n.child(2)) + if src, ok := n.child(3).opt(); ok { + // FromSource <- FromSourceIdentifier / FromSourceString + _, alt := src.sole().choice() + stmt.Repository = identifierText(alt.child(1)) + stmt.RepoIsAlias = alt.name() == "FromSourceIdentifier" + } + if ver, ok := n.child(4).opt(); ok { + // VersionNumber <- 'VERSION' IdentifierOrStringLiteral + stmt.Version = identifierText(ver.child(1)) + } + return stmt +} + +// ExternalResourceStatement <- CreateExternalResourceStmt / +// RegisterExternalResourceStmt / DestroyExternalResourceStmt / ShowExternalResourcesStmt +func (tc *transformContext) transformExternalResource(n tnode) ast.Stmt { + stmt := &ast.ExternalResourceStatement{} + stmt.SetSpan(n.span()) + _, alt := n.sole().choice() + switch alt.name() { + case "CreateExternalResourceStmt": + // 'CREATE' 'EXTERNAL' 'RESOURCE' StringLiteral AttachAlias? ExternalResourceCreationOptions? + stmt.Operation = ast.ExternalResourceCreate + stmt.Type = identifierText(alt.child(3)) + if alias, ok := alt.child(4).opt(); ok { + stmt.Name = identifierText(alias.child(1)) + } + if opts, ok := alt.child(5).opt(); ok { + // a bare flag binds to boolean true, mirroring upstream + for _, o := range tc.transformGenericOptionList(opts.sole()) { + if o.Expr == nil && len(o.Values) == 0 { + o.Values = []ast.Value{{Type: ast.LogicalType{ID: "BOOLEAN"}, Kind: ast.ValueBool, Bool: true}} + } + stmt.Options = append(stmt.Options, o) + } + } + case "RegisterExternalResourceStmt": + // 'REGISTER' 'EXTERNAL' 'RESOURCE' StringLiteral AttachAlias? 'FROM' Expression + stmt.Operation = ast.ExternalResourceRegister + stmt.Type = identifierText(alt.child(3)) + if alias, ok := alt.child(4).opt(); ok { + stmt.Name = identifierText(alias.child(1)) + } + stmt.Handle = tc.transformExpression(alt.child(6)) + case "DestroyExternalResourceStmt": + // 'DESTROY' 'EXTERNAL' 'RESOURCE' ColId + stmt.Operation = ast.ExternalResourceDestroy + stmt.Name = identifierText(alt.child(3)) + case "ShowExternalResourcesStmt": + // 'SHOW' ShowAllModifier? 'EXTERNAL' 'RESOURCES' + stmt.Operation = ast.ExternalResourceShow + _, stmt.All = alt.child(1).opt() + default: + shapeError(alt, "unknown external resource statement") + } + return stmt +} diff --git a/parser/transform_select.go b/parser/transform_select.go index ef06f28..ebc6dd5 100644 --- a/parser/transform_select.go +++ b/parser/transform_select.go @@ -54,8 +54,14 @@ func (tc *transformContext) transformSelectInternal(n tnode) ast.QueryNode { func (tc *transformContext) transformWithClause(n tnode) ast.CTEMap { _, recursive := n.child(1).opt() var out ast.CTEMap + seen := map[string]bool{} for _, ws := range n.child(2).listElems() { name, cte := tc.transformWithStatement(ws, recursive) + key := strings.ToLower(name) + if seen[key] { + raise("Duplicate CTE name \"%s\"", name) + } + seen[key] = true out.Entries = append(out.Entries, ast.CTEMapEntry{Name: name, CTE: cte}) } return out @@ -94,18 +100,47 @@ func (tc *transformContext) transformWithStatement(n tnode, recursive bool) (str shapeError(body, "unknown CTE body") } if recursive { - if setop, ok := cte.Query.(*ast.SetOperationNode); ok && setop.SetOpType == ast.SetOpUnion && len(setop.Inputs) == 2 { + // port of ValidateRecursiveCTEQueryNode + switch cte.Query.(type) { + case *ast.CopyQueryNode: + raise("Recursive CTEs with COPY statements are not supported") + case *ast.InsertQueryNode, *ast.UpdateQueryNode, *ast.DeleteQueryNode: + raise("Recursive CTEs with DML statements are not supported") + } + if setop, ok := cte.Query.(*ast.SetOperationNode); ok && setop.SetOpType == ast.SetOpUnion && len(setop.Inputs) >= 2 { + // port of ToRecursiveCTE: modifiers on the union are invalid + // (upstream checks the LIMIT and ORDER modifier types only, so + // LIMIT n% passes — an upstream quirk kept as-is) + for _, m := range setop.Modifiers { + switch mod := m.(type) { + case *ast.LimitModifier: + if mod.LimitType != ast.LimitPercentage { + raise("LIMIT or OFFSET in a recursive query is not allowed") + } + case *ast.OrderModifier: + raise("ORDER BY in a recursive query is not allowed") + } + } rec := &ast.RecursiveCTENode{ CTEName: name, UnionAll: setop.SetOpAll, - Left: setop.Inputs[0], - Right: setop.Inputs[1], Aliases: cte.Aliases, KeyTargets: cte.KeyTargets, } - rec.Modifiers = setop.Modifiers rec.CTEs = setop.CTEs + setop.CTEs = ast.CTEMap{} rec.SetSpan(ast.Span{Start: setop.Pos(), End: setop.End()}) + if len(setop.Inputs) == 2 { + // the union's own (surviving) modifiers are dropped, like + // upstream's binary case + rec.Left, rec.Right = setop.Inputs[0], setop.Inputs[1] + } else { + // n-ary flattened union: right = last input, left = the + // rest (keeping its modifiers) + rec.Right = setop.Inputs[len(setop.Inputs)-1] + setop.Inputs = setop.Inputs[:len(setop.Inputs)-1] + rec.Left = setop + } cte.Query = rec } } @@ -142,6 +177,13 @@ func dmlQueryNode(stmt ast.Stmt) ast.QueryNode { n := &ast.DeleteQueryNode{Delete: del} n.SetSpan(stmtSpan) return n + case *ast.CopyStatement: + if s.Info != nil && s.Info.IsFrom { + raise("COPY FROM cannot be used as a CTE body") + } + n := &ast.CopyQueryNode{Copy: s.Info} + n.SetSpan(stmtSpan) + return n } raise("A CTE body must be a SELECT, INSERT, UPDATE, DELETE, or COPY TO statement") return nil @@ -290,7 +332,9 @@ func (tc *transformContext) transformSimpleSelect(n tnode) ast.QueryNode { FrameStart: ast.WindowUnboundedPreceding, FrameEnd: ast.WindowCurrentRowRange, ExcludeClause: ast.WindowExcludeNoOther, } + tc.inWindowDefinition = true tc.transformWindowFrameDefinition(w, def.child(2)) + tc.inWindowDefinition = false tc.defineWindow(identifierText(def.child(0)), w) } } @@ -323,10 +367,12 @@ func (tc *transformContext) transformSimpleSelect(n tnode) ast.QueryNode { if dc, ok := selectClause.child(1).opt(); ok { tc.applyDistinct(node, dc) } - if tl, ok := selectClause.child(2).opt(); ok { - for _, e := range tl.listElems() { - node.SelectList = append(node.SelectList, tc.transformAliasedExpression(e)) - } + tl, hasTargets := selectClause.child(2).opt() + if !hasTargets { + raise("SELECT clause without selection list") + } + for _, e := range tl.listElems() { + node.SelectList = append(node.SelectList, tc.transformAliasedExpression(e)) } } else { star := &ast.StarExpression{} @@ -416,6 +462,7 @@ func (tc *transformContext) transformGroupBy(node *ast.SelectNode, n tnode) { sets := []ast.GroupingSet{{}} for _, entry := range alt.sole().listElems() { entrySets := tc.transformGroupByExpressionEntry(node, entry) + checkGroupingSetMax(len(sets) * len(entrySets)) var combined []ast.GroupingSet for _, base := range sets { for _, es := range entrySets { @@ -427,6 +474,15 @@ func (tc *transformContext) transformGroupBy(node *ast.SelectNode, n tnode) { node.GroupSets = sets } +// maxGroupingSets is upstream's grouping set cap. +const maxGroupingSets = 65535 + +func checkGroupingSetMax(count int) { + if count > maxGroupingSets { + raise("Maximum grouping set count of %d exceeded", maxGroupingSets) + } +} + func mergeSet(a, b ast.GroupingSet) ast.GroupingSet { return normalizeSet(append(append(ast.GroupingSet{}, a...), b...)) } @@ -508,7 +564,16 @@ func (tc *transformContext) transformGroupByExpressionEntry(node *ast.SelectNode units = append(units, tc.groupByUnit(node, tc.transformExpression(e))) } } + if len(units) == 0 { + raise("CUBE or ROLLUP column list cannot be empty") + } if kind.name() == "CubeKeyword" { + // port of CheckGroupingSetCubes: cap before expanding + combinations := 1 + for range units { + combinations *= 2 + checkGroupingSetMax(combinations) + } return normalizeSets(cubeSets(units)) } return normalizeSets(rollupSets(units)) @@ -840,6 +905,9 @@ func (tc *transformContext) applySampleCount(s *ast.SampleOptions, n tnode, meth if err != nil { raise("invalid sample size") } + if f < 0 || f > 100 { + raise("Sample sample_size %f out of range, must be between 0 and 100", f) + } s.SampleSize = ast.Value{Type: ast.LogicalType{ID: "DOUBLE"}, Kind: ast.ValueDouble, Float64: f} if method == "" { method = "system" @@ -849,6 +917,11 @@ func (tc *transformContext) applySampleCount(s *ast.SampleOptions, n tnode, meth if err != nil { raise("invalid sample size") } + // upstream caps row counts at SampleOptions::MAX_SAMPLE_ROWS + const maxSampleRows = 1000000000 + if iv < 0 || iv > maxSampleRows { + raise("Sample rows %d out of range, must be between 0 and %d", iv, maxSampleRows) + } s.SampleSize = ast.Value{Type: ast.LogicalType{ID: "BIGINT"}, Kind: ast.ValueInt64, Int64: iv} if method == "" { method = "reservoir" diff --git a/parser/transform_single.go b/parser/transform_single.go index c55ccb6..95d1241 100644 --- a/parser/transform_single.go +++ b/parser/transform_single.go @@ -9,6 +9,7 @@ import ( "math" "strconv" "strings" + "unicode/utf8" "github.com/sqlc-dev/darkwing/ast" "github.com/sqlc-dev/darkwing/internal/matcher" @@ -114,6 +115,9 @@ func (tc *transformContext) transformSingle(n tnode) ast.Expr { if err != nil { raise("invalid positional reference") } + if idx == 0 { + raise("Positional reference node needs to be >= 1") + } p := &ast.PositionalReferenceExpression{Index: idx} p.SetSpan(alt.span()) return p @@ -183,7 +187,15 @@ func (tc *transformContext) stringConstant(sp ast.Span, s string, kind matcher.S cast.SetSpan(sp) return cast case matcher.StringEscape: - return constExpr(sp, varcharValue(unescapeString(s))) + unescaped := unescapeString(s) + if strings.IndexByte(unescaped, 0) >= 0 { + raise("Null character not permitted in escape string literal") + } + if !utf8.ValidString(unescaped) { + raise("Invalid UTF-8 in escape string literal at byte offset %d: invalid unicode codepoint", + invalidUTF8Offset(unescaped)) + } + return constExpr(sp, varcharValue(unescaped)) default: return constExpr(sp, varcharValue(s)) } @@ -238,6 +250,19 @@ func unescapeString(s string) string { return sb.String() } +// invalidUTF8Offset finds the first invalid byte offset of a non-UTF-8 +// string (for the escape-literal error message). +func invalidUTF8Offset(s string) int { + for i := 0; i < len(s); { + r, size := utf8.DecodeRuneInString(s[i:]) + if r == utf8.RuneError && size == 1 { + return i + } + i += size + } + return len(s) +} + func isHexDigit(c byte) bool { return c >= '0' && c <= '9' || c >= 'a' && c <= 'f' || c >= 'A' && c <= 'F' } @@ -533,6 +558,7 @@ func (tc *transformContext) transformStar(n tnode) ast.Expr { } star.RelationName = strings.Join(parts, ".") } + var excludePaths [][]string if excl, ok := n.child(2).opt(); ok { // ExcludeList <- ExcludeOrExcept ExcludeNames _, names := excl.child(1).sole().choice() @@ -544,14 +570,28 @@ func (tc *transformContext) transformStar(n tnode) ast.Expr { } for _, e := range entries { path := excludeNamePath(e) + for _, prev := range excludePaths { + if qualifiedColumnsMatch(prev, path) { + raise("Duplicate entry \"%s\" in EXCLUDE list", strings.Join(path, ".")) + } + } + excludePaths = append(excludePaths, path) if len(path) == 1 { star.ExcludeList = append(star.ExcludeList, path[0]) } else { star.QualifiedExcludeList = append(star.QualifiedExcludeList, ast.QualifiedColumnName{Path: path}) } } - } + inExclude := func(path []string) bool { + for _, prev := range excludePaths { + if qualifiedColumnsMatch(prev, path) { + return true + } + } + return false + } + replaced := map[string]bool{} if repl, ok := n.child(3).opt(); ok { // ReplaceList <- 'REPLACE' ReplaceEntries _, entriesAlt := repl.child(1).sole().choice() @@ -569,7 +609,17 @@ func (tc *transformContext) transformStar(n tnode) ast.Expr { if !ok || len(cr.ColumnNames) != 1 { raise("REPLACE target must be a single column name") } - star.ReplaceList = append(star.ReplaceList, ast.StarReplaceEntry{Key: cr.ColumnNames[0], Expr: expr}) + key := cr.ColumnNames[0] + if replaced[strings.ToLower(key)] { + raise("Duplicate entry \"%s\" in REPLACE list", key) + } + replaced[strings.ToLower(key)] = true + star.ReplaceList = append(star.ReplaceList, ast.StarReplaceEntry{Key: key, Expr: expr}) + } + for _, r := range star.ReplaceList { + if inExclude([]string{r.Key}) { + raise("Column \"%s\" cannot occur in both EXCLUDE and REPLACE list", r.Key) + } } } if ren, ok := n.child(4).opt(); ok { @@ -589,10 +639,41 @@ func (tc *transformContext) transformStar(n tnode) ast.Expr { Name: identifierText(e.child(2)), }) } + for _, r := range star.RenameList { + if inExclude(r.Key.Path) { + raise("Column \"%s\" cannot occur in both EXCLUDE and RENAME list", strings.Join(r.Key.Path, ".")) + } + if replaced[strings.ToLower(r.Key.Path[len(r.Key.Path)-1])] { + raise("Column \"%s\" cannot occur in both REPLACE and RENAME list", strings.Join(r.Key.Path, ".")) + } + } } return star } +// qualifiedColumnsMatch ports upstream's QualifiedColumnEquality: dotted +// exclude/rename paths compare by column name, with missing qualifiers +// acting as wildcards ("tbl.i" matches "i" but not "tbl2.i"). +func qualifiedColumnsMatch(a, b []string) bool { + if !strings.EqualFold(a[len(a)-1], b[len(b)-1]) { + return false + } + aq, bq := a[:len(a)-1], b[:len(b)-1] + for i := 1; i <= 3; i++ { + var av, bv string + if len(aq) >= i { + av = aq[len(aq)-i] + } + if len(bq) >= i { + bv = bq[len(bq)-i] + } + if av != "" && bv != "" && !strings.EqualFold(av, bv) { + return false + } + } + return true +} + // excludeNamePath extracts the dotted path of an ExcludeName. func excludeNamePath(n tnode) []string { _, alt := n.sole().choice() diff --git a/parser/transform_tableref.go b/parser/transform_tableref.go index e45512f..d1892c1 100644 --- a/parser/transform_tableref.go +++ b/parser/transform_tableref.go @@ -291,6 +291,13 @@ func (tc *transformContext) transformValuesClause(n tnode) *ast.ExpressionListRe for _, row := range n.child(1).listElems() { ref.Values = append(ref.Values, tc.transformExpressionList(row.sole().parens())) } + for _, row := range ref.Values[1:] { + if len(row) != len(ref.Values[0]) { + raise("VALUES lists must all be the same length, expected %d %s but found a list with %d %s", + len(ref.Values[0]), plural(len(ref.Values[0]), "entry", "entries"), + len(row), plural(len(row), "entry", "entries")) + } + } return ref } @@ -320,25 +327,7 @@ func (tc *transformContext) transformTableFunction(n tnode) ast.TableRef { if _, ok := ordinality.opt(); ok { ref.WithOrdinality = true } - // QualifiedTableFunction <- CatalogQualification? SchemaQualification? TableFunctionName - fn := &ast.FunctionExpression{} - fn.SetSpan(invalidSpan) - if cq, ok := qualified.child(0).opt(); ok { - fn.Catalog = identifierText(cq.child(0)) - } - if sq, ok := qualified.child(1).opt(); ok { - fn.Schema = identifierText(sq.child(0)) - } - if fn.Catalog != "" && fn.Schema == "" { - fn.Catalog, fn.Schema = "", fn.Catalog - } - fn.FunctionName = strings.ToLower(identifierText(qualified.child(2))) - // TableFunctionArguments <- Parens(List(FunctionArgument)?) - if list, ok := args.sole().parens().opt(); ok { - for _, a := range list.listElems() { - fn.Arguments = append(fn.Arguments, tc.transformFunctionArgument(a)) - } - } + fn := tc.qualifiedFunctionExpr(qualified, args) // values(...) is a VALUES list, wrapped like any FROM-position VALUES if fn.FunctionName == "values" && fn.Schema == "" && fn.Catalog == "" { el := &ast.ExpressionListRef{} @@ -358,6 +347,31 @@ func (tc *transformContext) transformTableFunction(n tnode) ast.TableRef { return ref } +// qualifiedFunctionExpr builds the call expression for a qualified table +// function plus its argument list (shared by table functions and CALL). +// QualifiedTableFunction <- CatalogQualification? SchemaQualification? TableFunctionName +// TableFunctionArguments <- Parens(List(FunctionArgument)?) +func (tc *transformContext) qualifiedFunctionExpr(qualified, args tnode) *ast.FunctionExpression { + fn := &ast.FunctionExpression{} + fn.SetSpan(invalidSpan) + if cq, ok := qualified.child(0).opt(); ok { + fn.Catalog = identifierText(cq.child(0)) + } + if sq, ok := qualified.child(1).opt(); ok { + fn.Schema = identifierText(sq.child(0)) + } + if fn.Catalog != "" && fn.Schema == "" { + fn.Catalog, fn.Schema = "", fn.Catalog + } + fn.FunctionName = strings.ToLower(identifierText(qualified.child(2))) + if list, ok := args.sole().parens().opt(); ok { + for _, a := range list.listElems() { + fn.Arguments = append(fn.Arguments, tc.transformFunctionArgument(a)) + } + } + return fn +} + // ---- joins ------------------------------------------------------------- // JoinOrPivot <- JoinClause / TablePivotClause / TableUnpivotClause @@ -488,6 +502,9 @@ func (tc *transformContext) transformNearestJoin(left ast.TableRef, n tnode) ast if jt, ok := alt.child(0).opt(); ok { join.JoinType = joinTypeOf(jt) } + if join.JoinType != ast.JoinInner && join.JoinType != ast.JoinLeft { + raise("NEAREST BY only supports INNER and LEFT OUTER joins, not %s", string(join.JoinType)) + } if ae, ok := alt.child(3).opt(); ok { _, kind := ae.sole().choice() join.NearestApprox = kind.name() == "NearestApprox" @@ -732,6 +749,9 @@ func (tc *transformContext) transformTableUnpivot(left ast.TableRef, n tnode) as // UnpivotValueList <- UnpivotHeader 'IN' UnpivotTargetList var col ast.PivotColumn col.UnpivotNames = tc.transformUnpivotHeader(uv.child(0)) + if len(col.UnpivotNames) != 1 { + raise("UNPIVOT requires a single column name for the PIVOT IN clause") + } // UnpivotTargetList <- Parens(TargetList) for _, e := range uv.child(2).parens().listElems() { col.Entries = append(col.Entries, tc.unpivotEntry(e)) @@ -768,6 +788,32 @@ func (tc *transformContext) transformPivotStatement(n tnode) ast.QueryNode { for _, e := range on.child(1).sole().listElems() { ref.Pivots = append(ref.Pivots, tc.transformPivotColumnEntry(e)) } + // port of TransformPivotColumnList's checks on the ON expressions + for _, col := range ref.Pivots { + for _, e := range col.PivotExpressions { + if isScalarExpr(e) { + raise("Cannot pivot on constant value \"%s\"", constantText(e)) + } + if hasSubquery(e) { + raise("Cannot pivot on subquery \"%s\"", constantText(e)) + } + } + } + // pivot columns without an IN list or enum extract their values + // from the data; upstream records them as pivot entries (enum + // expansion), darkwing counts them for the CREATE VIEW/MACRO + // check. Parameters seen before this point (the pivot source) mix + // with data extraction and are rejected at the statement level; + // parameters later in the statement (USING aggregates) are fine, + // matching upstream's transform order. + for _, col := range ref.Pivots { + if col.PivotEnum == "" && len(col.Entries) == 0 && len(col.PivotExpressions) > 0 { + tc.pivotEntries++ + if tc.paramCount > 0 || tc.hasNamed || tc.hasPosition { + tc.pivotEntryHasParams = true + } + } + } } if using, ok := n.child(3).opt(); ok { for _, e := range using.child(1).listElems() { @@ -809,6 +855,49 @@ func (tc *transformContext) transformPivotStatement(n tnode) ast.QueryNode { return starSelect(ref, n.span()) } +// isScalarExpr ports ParsedExpression::IsScalar: true when the tree +// contains no column/positional references, defaults or subqueries +// (parameters count as scalar upstream). +func isScalarExpr(e ast.Expr) bool { + switch e.(type) { + case nil: + return true + case *ast.ColumnRefExpression, *ast.DefaultExpression, + *ast.PositionalReferenceExpression, *ast.SubqueryExpression: + return false + } + for _, child := range e.Children() { + if expr, ok := child.(ast.Expr); ok && !isScalarExpr(expr) { + return false + } + } + return true +} + +// constantText renders an expression for an error message, best-effort +// (upstream calls the full ToString renderer, which darkwing does not +// have; only constants are spelled out). +func constantText(e ast.Expr) string { + c, ok := e.(*ast.ConstantExpression) + if !ok { + return "?" + } + switch { + case c.Value.IsNull: + return "NULL" + case c.Value.Kind == ast.ValueString: + return "'" + c.Value.Str + "'" + case c.Value.Kind == ast.ValueInt64: + return strconv.FormatInt(c.Value.Int64, 10) + case c.Value.Kind == ast.ValueBool: + if c.Value.Bool { + return "true" + } + return "false" + } + return "?" +} + // PivotColumnEntry <- PivotColumnSubquery / PivotValueList / PivotColumnExpression func (tc *transformContext) transformPivotColumnEntry(n tnode) ast.PivotColumn { _, alt := n.sole().choice() diff --git a/parser/transform_type.go b/parser/transform_type.go index d947ac9..5477fb6 100644 --- a/parser/transform_type.go +++ b/parser/transform_type.go @@ -52,9 +52,17 @@ func (tc *transformContext) squareBracketArray(base *ast.TypeExpression, n tnode return typeExpr(sp, "list", base) } bound := tc.transformExpression(expr) - // a constant integer bound is re-synthesized as a location-free - // BIGINT value - if c, isConst := bound.(*ast.ConstantExpression); isConst && c.Value.Kind == ast.ValueInt64 { + // the bound must be a constant; a constant integer is re-synthesized + // as a location-free BIGINT value, and negative sizes are a parser + // error upstream (non-integer constants fail at conversion, post-parse) + c, isConst := bound.(*ast.ConstantExpression) + if !isConst { + raise("Expected a constant number as array size") + } + if c.Value.Kind == ast.ValueInt64 { + if c.Value.Int64 < 0 { + raise("Array size must be greater than 0") + } c.Value.Type = ast.LogicalType{ID: "BIGINT"} c.SetSpan(invalidSpan) } @@ -100,7 +108,11 @@ func (tc *transformContext) transformTypeVariation(n tnode) *ast.TypeExpression case "GeometryType": var args []ast.Expr if mod, ok := alt.child(1).opt(); ok { - args = append(args, tc.transformExpression(mod.parens())) + e := tc.transformExpression(mod.parens()) + if _, isConst := e.(*ast.ConstantExpression); !isConst { + raise("Expected a constant as type modifier") + } + args = append(args, e) } return typeExpr(sp, "GEOMETRY", args...) case "UnionType": @@ -134,21 +146,33 @@ func (tc *transformContext) transformColIdTypeList(n tnode) []ast.Expr { func (tc *transformContext) transformTimeType(n tnode) *ast.TypeExpression { _, kind := n.child(0).sole().choice() name := "TIME" - if kind.name() == "TimestampTypeId" { + if kind.name() != "TimestampTypeId" { + if _, hasMods := n.child(1).opt(); hasMods { + raise("Type TIME does not allow any modifiers") + } + } else { name = "TIMESTAMP" // a precision modifier picks the timestamp variant if mods, ok := n.child(1).opt(); ok { args := tc.transformTypeModifiers(mods) + if len(args) > 1 { + raise("TIMESTAMP only supports a single modifier") + } if len(args) == 1 { if c, isConst := args[0].(*ast.ConstantExpression); isConst && c.Value.Kind == ast.ValueInt64 { - switch c.Value.Int64 { - case 0: + p := c.Value.Int64 + switch { + case p > 10: + raise("TIMESTAMP only supports until nano-second precision (9)") + case p < 0: + raise("TIMESTAMP precision should be between 0 and 10 (inclusive)") + case p == 0: name = "TIMESTAMP_S" - case 3: + case p <= 3: name = "TIMESTAMP_MS" - case 6: + case p <= 6: name = "TIMESTAMP" - case 9: + default: name = "TIMESTAMP_NS" } } @@ -235,7 +259,13 @@ func (tc *transformContext) transformTypeModifiers(n tnode) []ast.Expr { if !ok { return nil } - return tc.transformExpressionList(list) + mods := tc.transformExpressionList(list) + for _, m := range mods { + if _, isConst := m.(*ast.ConstantExpression); !isConst { + raise("Expected a constant as type modifier") + } + } + return mods } // SimpleType <- CharacterSimpleType / QualifiedSimpleType diff --git a/parser/tree.go b/parser/tree.go index a3f5b04..58da357 100644 --- a/parser/tree.go +++ b/parser/tree.go @@ -37,6 +37,15 @@ func (e *internalError) Error() string { func (n tnode) valid() bool { return n.r != nil } +// plural picks the singular or plural spelling for a count (error +// message helper). +func plural(count int, one, many string) string { + if count == 1 { + return one + } + return many +} + func (n tnode) name() string { if n.r == nil { return ""