From 3e964a2f4e0b8245bb39b9a6dc93d9988ebcd420 Mon Sep 17 00:00:00 2001 From: Ajit Pratap Singh Date: Mon, 26 Oct 2026 13:03:54 +0530 Subject: [PATCH 1/2] test(realworld): add multi-dialect real-world corpus with ratchet Add 2,788 production-style statements under testdata/realworld for PostgreSQL, MySQL, MariaDB, SQL Server, Oracle, SQLite, Snowflake and ClickHouse. TestRealWorldCorpus runs each through split, parse, format, reparse, idempotency and dropped-word checks against known_failures.txt, which records the 1,327 statements that fail on main today. A new failure and a listed failure that now passes both fail the test. Also add scripts/dialect-audit (the same checks for any SQL file), internal/dropcheck (the shared dropped-word detector) and the BenchmarkRealWorldParse / BenchmarkRealWorldParseFormat benchmarks. The formatter's testdata walker skips the corpus until the dialect fixes land. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01DnbRnTFErhoMJdAjVEeCg2 --- CHANGELOG.md | 11 + internal/dropcheck/dropcheck.go | 148 ++ internal/dropcheck/dropcheck_test.go | 85 ++ pkg/formatter/nested_semicolon_test.go | 6 + pkg/gosqlx/realworld_bench_test.go | 102 ++ pkg/gosqlx/realworld_corpus_test.go | 202 +++ scripts/dialect-audit/main.go | 143 ++ testdata/realworld/clickhouse/01_app_crud.sql | 26 + .../realworld/clickhouse/02_reporting.sql | 32 + .../clickhouse/03_ddl_migrations.sql | 48 + .../clickhouse/04_window_analytics.sql | 32 + .../clickhouse/05_dialect_specific.sql | 73 + .../clickhouse/06_materialized_views.sql | 37 + .../realworld/clickhouse/07_observability.sql | 47 + .../realworld/clickhouse/08_etl_pipelines.sql | 41 + .../realworld/clickhouse/09_deep_nesting.sql | 38 + .../realworld/clickhouse/10_misc_real.sql | 29 + testdata/realworld/known_failures.txt | 1331 +++++++++++++++++ testdata/realworld/mariadb/01_app_crud.sql | 36 + testdata/realworld/mariadb/02_reporting.sql | 46 + .../realworld/mariadb/03_ddl_migrations.sql | 55 + .../realworld/mariadb/04_window_analytics.sql | 24 + .../realworld/mariadb/05_mariadb_specific.sql | 71 + testdata/realworld/mariadb/06_etl_admin.sql | 42 + .../realworld/mariadb/07_stored_programs.sql | 20 + .../realworld/mariadb/08_complex_queries.sql | 32 + .../mariadb/09_mysql_compat_misc.sql | 57 + testdata/realworld/mysql/01_app_crud.sql | 32 + testdata/realworld/mysql/02_reporting.sql | 36 + .../realworld/mysql/03_ddl_migrations.sql | 45 + .../realworld/mysql/04_window_analytics.sql | 23 + .../realworld/mysql/05_dialect_specific.sql | 74 + testdata/realworld/mysql/06_wordpress.sql | 22 + .../realworld/mysql/07_laravel_magento.sql | 17 + testdata/realworld/mysql/08_etl_admin.sql | 38 + testdata/realworld/mysql/09_deep_nesting.sql | 35 + .../realworld/mysql/10_misc_functions.sql | 31 + .../realworld/mysql/11_stored_programs.sql | 37 + testdata/realworld/mysql/12_orm_misc.sql | 27 + testdata/realworld/oracle/01_app_crud.sql | 39 + testdata/realworld/oracle/02_reporting.sql | 38 + .../realworld/oracle/03_ddl_migrations.sql | 62 + .../realworld/oracle/04_window_analytics.sql | 29 + .../realworld/oracle/05_dialect_specific.sql | 73 + testdata/realworld/oracle/06_etl_merge.sql | 39 + testdata/realworld/oracle/07_admin_ops.sql | 53 + .../realworld/oracle/08_complex_nested.sql | 27 + testdata/realworld/oracle/09_plsql_blocks.sql | 116 ++ testdata/realworld/postgresql/01_app_crud.sql | 27 + .../realworld/postgresql/02_reporting.sql | 29 + .../postgresql/03_ddl_migrations.sql | 67 + .../postgresql/04_window_analytics.sql | 23 + .../postgresql/05_dialect_specific.sql | 58 + .../realworld/postgresql/06_cte_dml_etl.sql | 28 + .../realworld/postgresql/07_admin_ops.sql | 46 + .../postgresql/08_supabase_rls_misc.sql | 30 + testdata/realworld/snowflake/01_app_crud.sql | 23 + testdata/realworld/snowflake/02_reporting.sql | 28 + .../realworld/snowflake/03_ddl_migrations.sql | 38 + .../snowflake/04_window_analytics.sql | 19 + .../snowflake/05_dialect_specific.sql | 53 + testdata/realworld/snowflake/06_etl_merge.sql | 37 + testdata/realworld/snowflake/07_admin.sql | 54 + .../realworld/snowflake/08_pivot_match.sql | 24 + .../realworld/snowflake/09_dbt_models.sql | 19 + .../realworld/snowflake/10_complex_nested.sql | 41 + testdata/realworld/sqlite/01_app_crud.sql | 37 + .../realworld/sqlite/02_upsert_replace.sql | 32 + .../realworld/sqlite/03_ddl_migrations.sql | 74 + .../realworld/sqlite/04_window_analytics.sql | 26 + .../realworld/sqlite/05_dialect_specific.sql | 70 + testdata/realworld/sqlite/06_json.sql | 29 + .../realworld/sqlite/07_ctes_recursive.sql | 36 + testdata/realworld/sqlite/08_reporting.sql | 32 + testdata/realworld/sqlite/09_etl_admin.sql | 42 + .../sqlite/10_mobile_offline_sync.sql | 46 + testdata/realworld/sqlite/11_deep_nesting.sql | 35 + .../realworld/sqlite/12_orm_generated.sql | 21 + testdata/realworld/sqlserver/01_app_crud.sql | 27 + testdata/realworld/sqlserver/02_reporting.sql | 27 + .../realworld/sqlserver/03_ddl_migrations.sql | 33 + .../sqlserver/04_window_analytics.sql | 18 + .../sqlserver/05_dialect_specific.sql | 36 + testdata/realworld/sqlserver/06_merge_etl.sql | 20 + testdata/realworld/sqlserver/07_ctes_deep.sql | 22 + .../realworld/sqlserver/08_procs_batches.sql | 33 + testdata/realworld/sqlserver/09_admin_dmv.sql | 21 + testdata/realworld/sqlserver/10_etl_mixed.sql | 32 + .../sqlserver/11_batches_no_semicolons.sql | 104 ++ 89 files changed, 5204 insertions(+) create mode 100644 internal/dropcheck/dropcheck.go create mode 100644 internal/dropcheck/dropcheck_test.go create mode 100644 pkg/gosqlx/realworld_bench_test.go create mode 100644 pkg/gosqlx/realworld_corpus_test.go create mode 100644 scripts/dialect-audit/main.go create mode 100644 testdata/realworld/clickhouse/01_app_crud.sql create mode 100644 testdata/realworld/clickhouse/02_reporting.sql create mode 100644 testdata/realworld/clickhouse/03_ddl_migrations.sql create mode 100644 testdata/realworld/clickhouse/04_window_analytics.sql create mode 100644 testdata/realworld/clickhouse/05_dialect_specific.sql create mode 100644 testdata/realworld/clickhouse/06_materialized_views.sql create mode 100644 testdata/realworld/clickhouse/07_observability.sql create mode 100644 testdata/realworld/clickhouse/08_etl_pipelines.sql create mode 100644 testdata/realworld/clickhouse/09_deep_nesting.sql create mode 100644 testdata/realworld/clickhouse/10_misc_real.sql create mode 100644 testdata/realworld/known_failures.txt create mode 100644 testdata/realworld/mariadb/01_app_crud.sql create mode 100644 testdata/realworld/mariadb/02_reporting.sql create mode 100644 testdata/realworld/mariadb/03_ddl_migrations.sql create mode 100644 testdata/realworld/mariadb/04_window_analytics.sql create mode 100644 testdata/realworld/mariadb/05_mariadb_specific.sql create mode 100644 testdata/realworld/mariadb/06_etl_admin.sql create mode 100644 testdata/realworld/mariadb/07_stored_programs.sql create mode 100644 testdata/realworld/mariadb/08_complex_queries.sql create mode 100644 testdata/realworld/mariadb/09_mysql_compat_misc.sql create mode 100644 testdata/realworld/mysql/01_app_crud.sql create mode 100644 testdata/realworld/mysql/02_reporting.sql create mode 100644 testdata/realworld/mysql/03_ddl_migrations.sql create mode 100644 testdata/realworld/mysql/04_window_analytics.sql create mode 100644 testdata/realworld/mysql/05_dialect_specific.sql create mode 100644 testdata/realworld/mysql/06_wordpress.sql create mode 100644 testdata/realworld/mysql/07_laravel_magento.sql create mode 100644 testdata/realworld/mysql/08_etl_admin.sql create mode 100644 testdata/realworld/mysql/09_deep_nesting.sql create mode 100644 testdata/realworld/mysql/10_misc_functions.sql create mode 100644 testdata/realworld/mysql/11_stored_programs.sql create mode 100644 testdata/realworld/mysql/12_orm_misc.sql create mode 100644 testdata/realworld/oracle/01_app_crud.sql create mode 100644 testdata/realworld/oracle/02_reporting.sql create mode 100644 testdata/realworld/oracle/03_ddl_migrations.sql create mode 100644 testdata/realworld/oracle/04_window_analytics.sql create mode 100644 testdata/realworld/oracle/05_dialect_specific.sql create mode 100644 testdata/realworld/oracle/06_etl_merge.sql create mode 100644 testdata/realworld/oracle/07_admin_ops.sql create mode 100644 testdata/realworld/oracle/08_complex_nested.sql create mode 100644 testdata/realworld/oracle/09_plsql_blocks.sql create mode 100644 testdata/realworld/postgresql/01_app_crud.sql create mode 100644 testdata/realworld/postgresql/02_reporting.sql create mode 100644 testdata/realworld/postgresql/03_ddl_migrations.sql create mode 100644 testdata/realworld/postgresql/04_window_analytics.sql create mode 100644 testdata/realworld/postgresql/05_dialect_specific.sql create mode 100644 testdata/realworld/postgresql/06_cte_dml_etl.sql create mode 100644 testdata/realworld/postgresql/07_admin_ops.sql create mode 100644 testdata/realworld/postgresql/08_supabase_rls_misc.sql create mode 100644 testdata/realworld/snowflake/01_app_crud.sql create mode 100644 testdata/realworld/snowflake/02_reporting.sql create mode 100644 testdata/realworld/snowflake/03_ddl_migrations.sql create mode 100644 testdata/realworld/snowflake/04_window_analytics.sql create mode 100644 testdata/realworld/snowflake/05_dialect_specific.sql create mode 100644 testdata/realworld/snowflake/06_etl_merge.sql create mode 100644 testdata/realworld/snowflake/07_admin.sql create mode 100644 testdata/realworld/snowflake/08_pivot_match.sql create mode 100644 testdata/realworld/snowflake/09_dbt_models.sql create mode 100644 testdata/realworld/snowflake/10_complex_nested.sql create mode 100644 testdata/realworld/sqlite/01_app_crud.sql create mode 100644 testdata/realworld/sqlite/02_upsert_replace.sql create mode 100644 testdata/realworld/sqlite/03_ddl_migrations.sql create mode 100644 testdata/realworld/sqlite/04_window_analytics.sql create mode 100644 testdata/realworld/sqlite/05_dialect_specific.sql create mode 100644 testdata/realworld/sqlite/06_json.sql create mode 100644 testdata/realworld/sqlite/07_ctes_recursive.sql create mode 100644 testdata/realworld/sqlite/08_reporting.sql create mode 100644 testdata/realworld/sqlite/09_etl_admin.sql create mode 100644 testdata/realworld/sqlite/10_mobile_offline_sync.sql create mode 100644 testdata/realworld/sqlite/11_deep_nesting.sql create mode 100644 testdata/realworld/sqlite/12_orm_generated.sql create mode 100644 testdata/realworld/sqlserver/01_app_crud.sql create mode 100644 testdata/realworld/sqlserver/02_reporting.sql create mode 100644 testdata/realworld/sqlserver/03_ddl_migrations.sql create mode 100644 testdata/realworld/sqlserver/04_window_analytics.sql create mode 100644 testdata/realworld/sqlserver/05_dialect_specific.sql create mode 100644 testdata/realworld/sqlserver/06_merge_etl.sql create mode 100644 testdata/realworld/sqlserver/07_ctes_deep.sql create mode 100644 testdata/realworld/sqlserver/08_procs_batches.sql create mode 100644 testdata/realworld/sqlserver/09_admin_dmv.sql create mode 100644 testdata/realworld/sqlserver/10_etl_mixed.sql create mode 100644 testdata/realworld/sqlserver/11_batches_no_semicolons.sql diff --git a/CHANGELOG.md b/CHANGELOG.md index 64cb71fd..fed25727 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- **Real-world dialect corpus**: `testdata/realworld//` holds 2,788 + production-style statements across PostgreSQL, MySQL, MariaDB, SQL Server, + Oracle, SQLite, Snowflake and ClickHouse. `TestRealWorldCorpus` runs each + through split, parse, format, reparse, idempotency and dropped-word checks + against a ratchet list (`testdata/realworld/known_failures.txt`, regenerate + with `-update-known-failures`): a new failure fails the test, and so does a + listed failure that now passes. `scripts/dialect-audit` runs the same checks + on any SQL file; `BenchmarkRealWorldParse` and `BenchmarkRealWorldParseFormat` + measure the corpus. + ### Fixed - **`ExtractColumns` / `ExtractColumnsQualified` no longer report SELECT aliases** (#849): an unqualified name in `ORDER BY` or `HAVING` that matches a diff --git a/internal/dropcheck/dropcheck.go b/internal/dropcheck/dropcheck.go new file mode 100644 index 00000000..f0a1b553 --- /dev/null +++ b/internal/dropcheck/dropcheck.go @@ -0,0 +1,148 @@ +// Copyright 2026 GoSQLX Authors +// SPDX-License-Identifier: Apache-2.0 + +// Package dropcheck is a cheap detector for clauses a formatter silently +// drops even though its output re-parses: it reports words of the input +// (outside comments and string literals) that are missing from the output. +// It is shared by the real-world corpus test and scripts/dialect-audit. +package dropcheck + +import ( + "regexp" + "strings" +) + +// DroppedWords reports words of the input (outside comments and string +// literals) that are missing from the formatted output — a cheap detector for +// clauses the formatter silently drops even though its output reparses. +func DroppedWords(in, out string) []string { + in = CanonicalSynonyms(in) + have := map[string]bool{} + for _, w := range Words(out) { + have[w] = true + } + var lost []string + seen := map[string]bool{} + for _, w := range Words(in) { + if !have[w] && !ignorable[w] && !seen[w] { + seen[w] = true + lost = append(lost, w) + } + } + return lost +} + +// synonyms matches spellings the formatter writes in their canonical +// form because the dialects define them as exact synonyms: SELECT ALL (ALL is +// the default quantifier), DISTINCTROW (MySQL) and SELECT UNIQUE (Oracle) for +// DISTINCT, and the postfix +// ISNULL / NOTNULL (PostgreSQL, SQLite) for IS NULL / IS NOT NULL. UNION ALL +// and the function ISNULL(...) are not touched. +var synonyms = regexp.MustCompile(`(?i)\b(SELECT\s+ALL|SELECT\s+UNIQUE|DISTINCTROW|ISNULL|NOTNULL)\b`) + +// CanonicalSynonyms rewrites the synonyms of synonyms in s +// to the canonical spelling the formatter writes. +func CanonicalSynonyms(s string) string { + var sb strings.Builder + last := 0 + for _, m := range synonyms.FindAllStringIndex(s, -1) { + word := strings.ToUpper(s[m[0]:m[1]]) + rest := strings.TrimLeft(s[m[1]:], " \t\r\n") + var repl string + switch { + case strings.HasPrefix(word, "SELECT") && strings.HasSuffix(word, "UNIQUE"): + repl = "SELECT DISTINCT" + case strings.HasPrefix(word, "SELECT"): + repl = "SELECT" + case word == "DISTINCTROW": + repl = "DISTINCT" + case strings.HasPrefix(rest, "("): // the ISNULL(...) function + continue + case word == "ISNULL": + repl = "IS NULL" + default: + repl = "IS NOT NULL" + } + sb.WriteString(s[last:m[0]]) + sb.WriteString(repl) + last = m[1] + } + sb.WriteString(s[last:]) + return sb.String() +} + +// ignorable are noise words the formatter may legitimately add or drop. +var ignorable = map[string]bool{"AS": true, "ASC": true, "INNER": true, "OUTER": true} + +// Words returns the upper-cased identifier-like words of s, skipping +// comments and quoted string literals but keeping quoted identifiers' text. +func Words(s string) []string { + var words []string + for i := 0; i < len(s); { + c := s[i] + switch { + case c == '-' && i+1 < len(s) && s[i+1] == '-': + for i < len(s) && s[i] != '\n' { + i++ + } + case c == '/' && i+1 < len(s) && s[i+1] == '*': + end := strings.Index(s[i+2:], "*/") + if end < 0 { + return words + } + i += end + 4 + case c == '$' && dollarQuoteEnd(s, i) > i: + i = dollarQuoteEnd(s, i) + case c == '\'': + for i++; i < len(s); i++ { + if s[i] == '\\' { + i++ + continue + } + if s[i] == '\'' { + if i+1 < len(s) && s[i+1] == '\'' { + i++ + continue + } + break + } + } + i++ + case c == '_' || c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c >= 0x80: + j := i + for j < len(s) && (s[j] == '_' || s[j] == '$' || s[j] >= '0' && s[j] <= '9' || + s[j] >= 'A' && s[j] <= 'Z' || s[j] >= 'a' && s[j] <= 'z' || s[j] >= 0x80) { + j++ + } + words = append(words, strings.ToUpper(s[i:j])) + i = j + case c >= '0' && c <= '9': + for i < len(s) && (s[i] == '.' || s[i] == '_' || s[i] >= '0' && s[i] <= '9' || + s[i] >= 'A' && s[i] <= 'Z' || s[i] >= 'a' && s[i] <= 'z') { + i++ + } + default: + i++ + } + } + return words +} + +// dollarQuoteEnd returns the offset just past the dollar-quoted +// string ($$...$$ or $tag$...$tag$) that starts at s[i], or i when s[i] does +// not start one. +func dollarQuoteEnd(s string, i int) int { + j := i + 1 + for j < len(s) && (s[j] == '_' || s[j] >= '0' && s[j] <= '9' || s[j] >= 'A' && s[j] <= 'Z' || s[j] >= 'a' && s[j] <= 'z') { + j++ + } + if j >= len(s) || s[j] != '$' || (j > i+1 && s[i+1] >= '0' && s[i+1] <= '9') { + return i + } + tag := s[i : j+1] + end := strings.Index(s[j+1:], tag) + if end < 0 { + return i + } + return j + 1 + end + len(tag) +} diff --git a/internal/dropcheck/dropcheck_test.go b/internal/dropcheck/dropcheck_test.go new file mode 100644 index 00000000..c4a7b5ae --- /dev/null +++ b/internal/dropcheck/dropcheck_test.go @@ -0,0 +1,85 @@ +// Copyright 2026 GoSQLX Authors +// SPDX-License-Identifier: Apache-2.0 + +package dropcheck + +import ( + "reflect" + "strings" + "testing" +) + +func TestDroppedWordsCanonicalSynonyms(t *testing.T) { + tests := []struct { + name string + in string + out string + want []string + }{ + {"select all", "SELECT ALL name FROM users", "SELECT name FROM users", nil}, + {"select unique", "SELECT UNIQUE name FROM users", "SELECT DISTINCT name FROM users", nil}, + {"distinctrow", "SELECT DISTINCTROW name FROM users", "SELECT DISTINCT name FROM users", nil}, + {"postfix isnull", "SELECT x ISNULL FROM users", "SELECT x IS NULL FROM users", nil}, + {"postfix notnull", "SELECT x NOTNULL FROM users", "SELECT x IS NOT NULL FROM users", nil}, + {"isnull function preserved", "SELECT ISNULL(x, y) FROM users", "SELECT x FROM users", []string{"ISNULL", "Y"}}, + {"union all retained", "SELECT x FROM a UNION ALL SELECT x FROM b", "SELECT x FROM a UNION SELECT x FROM b", []string{"ALL"}}, + {"dropped clause words", "SELECT x FROM users WHERE enabled = true", "SELECT x FROM users", []string{"WHERE", "ENABLED", "TRUE"}}, + {"dollar-quoted literal contents ignored", "SELECT $body$WHERE missing$body$ FROM users", "SELECT 'rewritten' FROM users", nil}, + {"quoted and commented words ignored", "SELECT x, 'missing string' FROM users -- missing comment", "SELECT x FROM users", nil}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := DroppedWords(tt.in, tt.out); !reflect.DeepEqual(got, tt.want) { + t.Errorf("DroppedWords(%q, %q) = %v, want %v", tt.in, tt.out, got, tt.want) + } + }) + } +} + +func TestSQLWordsSkipsDollarQuotedLiteral(t *testing.T) { + tests := []struct { + name string + sql string + want []string + }{ + {"untagged", "SELECT $$WHERE missing$$ FROM users", []string{"SELECT", "FROM", "USERS"}}, + {"tagged", "SELECT $body$WHERE missing$body$ FROM users", []string{"SELECT", "FROM", "USERS"}}, + {"regular identifier", "SELECT $1, my$column FROM users", []string{"SELECT", "MY$COLUMN", "FROM", "USERS"}}, + {"quoted identifier retained", "SELECT \"WHERE\" FROM users", []string{"SELECT", "WHERE", "FROM", "USERS"}}, + {"ordinary string and comments", "SELECT 'WHERE' FROM users /* missing */ -- other\nWHERE live", []string{"SELECT", "FROM", "USERS", "WHERE", "LIVE"}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := Words(tt.sql); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Words(%q) = %v, want %v", tt.sql, got, tt.want) + } + }) + } +} + +// TestDroppedWordsCorpusCases pins what the corpus drop detector accepts as a +// canonical spelling and what it still reports. +func TestDroppedWordsCorpusCases(t *testing.T) { + tests := []struct { + name, in, out string + lost string // space-separated, "" when nothing is lost + }{ + {"select all", "SELECT ALL a FROM t", "SELECT a FROM t", ""}, + {"union all is not a synonym", "SELECT a FROM t UNION ALL SELECT a FROM u", "SELECT a FROM t UNION SELECT a FROM u", "ALL"}, + {"oracle select unique", "SELECT UNIQUE a FROM t", "SELECT DISTINCT a FROM t", ""}, + {"distinctrow", "SELECT DISTINCTROW a FROM t", "SELECT DISTINCT a FROM t", ""}, + {"postfix isnull", "SELECT a FROM t WHERE a ISNULL", "SELECT a FROM t WHERE a IS NULL", ""}, + {"postfix notnull", "SELECT a FROM t WHERE a NOTNULL", "SELECT a FROM t WHERE a IS NOT NULL", ""}, + {"isnull function is not a synonym", "SELECT ISNULL(a, 0) FROM t", "SELECT a FROM t", "ISNULL"}, + {"dollar quoted strings", "SELECT $$dollar quoted$$, $tag$ body 'with' quotes $tag$", "SELECT 'dollar quoted', ' body ''with'' quotes '", ""}, + {"dropped clause", "SELECT a FROM t WHERE b = 1", "SELECT a FROM t", "WHERE B"}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got := strings.Join(DroppedWords(tc.in, tc.out), " ") + if got != tc.lost { + t.Errorf("lost words = %q, want %q", got, tc.lost) + } + }) + } +} diff --git a/pkg/formatter/nested_semicolon_test.go b/pkg/formatter/nested_semicolon_test.go index ecc12333..bc06fd9f 100644 --- a/pkg/formatter/nested_semicolon_test.go +++ b/pkg/formatter/nested_semicolon_test.go @@ -127,6 +127,12 @@ func TestFormatAST_ReadableSemicolonOnlyTerminatesStatements(t *testing.T) { if werr != nil { return werr } + // The real-world corpus has its own ratchet (TestRealWorldCorpus) and + // still holds statements whose ';' placement is wrong; the dialect + // fixes that repair them remove this skip. + if d.IsDir() && d.Name() == "realworld" { + return filepath.SkipDir + } if d.IsDir() || !strings.HasSuffix(path, ".sql") { return nil } diff --git a/pkg/gosqlx/realworld_bench_test.go b/pkg/gosqlx/realworld_bench_test.go new file mode 100644 index 00000000..be690c83 --- /dev/null +++ b/pkg/gosqlx/realworld_bench_test.go @@ -0,0 +1,102 @@ +// Copyright 2026 GoSQLX Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gosqlx + +import ( + "os" + "path/filepath" + "testing" + + "github.com/ajitpratap0/GoSQLX/pkg/sql/keywords" +) + +type realworldStmt struct { + dialect keywords.SQLDialect + sql string +} + +// loadRealworldParseable returns every statement of testdata/realworld that +// currently parses in its dialect, so the benchmarks measure the user path on +// production-shaped SQL rather than on synthetic one-liners. +func loadRealworldParseable(b *testing.B) ([]realworldStmt, int64) { + b.Helper() + dirs, err := os.ReadDir(realworldRoot) + if err != nil { + b.Skipf("testdata/realworld not available: %v", err) + } + var stmts []realworldStmt + var bytes int64 + for _, d := range dirs { + if !d.IsDir() { + continue + } + dialect := keywords.SQLDialect(d.Name()) + files, _ := filepath.Glob(filepath.Join(realworldRoot, d.Name(), "*.sql")) + for _, file := range files { + data, err := os.ReadFile(file) + if err != nil { + b.Fatalf("read %s: %v", file, err) + } + for _, stmt := range SplitStatements(string(data), d.Name()) { + if onlyComments(stmt) { + continue + } + if _, err := ParseWithDialect(stmt, dialect); err != nil { + continue + } + stmts = append(stmts, realworldStmt{dialect, stmt}) + bytes += int64(len(stmt)) + } + } + } + if len(stmts) == 0 { + b.Skip("no parseable real-world statements") + } + return stmts, bytes +} + +// BenchmarkRealWorldParse parses the whole parseable real-world corpus once +// per iteration with the statement's own dialect. +func BenchmarkRealWorldParse(b *testing.B) { + stmts, size := loadRealworldParseable(b) + b.SetBytes(size) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + for _, s := range stmts { + if _, err := ParseWithDialect(s.sql, s.dialect); err != nil { + b.Fatal(err) + } + } + } +} + +// BenchmarkRealWorldParseFormat measures the format path users run in the +// CLI and LSP: parse with dialect, then render. +func BenchmarkRealWorldParseFormat(b *testing.B) { + stmts, size := loadRealworldParseable(b) + b.SetBytes(size) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + for _, s := range stmts { + tree, err := ParseWithDialect(s.sql, s.dialect) + if err != nil { + b.Fatal(err) + } + _ = FormatAST(tree) + } + } +} diff --git a/pkg/gosqlx/realworld_corpus_test.go b/pkg/gosqlx/realworld_corpus_test.go new file mode 100644 index 00000000..dddcddc5 --- /dev/null +++ b/pkg/gosqlx/realworld_corpus_test.go @@ -0,0 +1,202 @@ +// Copyright 2026 GoSQLX Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gosqlx + +import ( + "bufio" + "flag" + "fmt" + "os" + "path/filepath" + "sort" + "strings" + "testing" + + "github.com/ajitpratap0/GoSQLX/internal/dropcheck" + "github.com/ajitpratap0/GoSQLX/pkg/sql/keywords" +) + +// realworldRoot holds one directory per dialect of real-world SQL; every +// statement is run through split → parse → format → reparse → reformat. +var realworldRoot = filepath.Join("..", "..", "testdata", "realworld") + +var updateKnownFailures = flag.Bool("update-known-failures", false, + "rewrite testdata/realworld/known_failures.txt from the current results") + +// TestRealWorldCorpus is a ratchet: a statement must pass every stage unless +// it is listed in testdata/realworld/known_failures.txt, and a listed +// statement that now passes fails the test so the list only ever shrinks. +func TestRealWorldCorpus(t *testing.T) { + dirs, err := os.ReadDir(realworldRoot) + if os.IsNotExist(err) { + t.Skip("testdata/realworld not present") + } + if err != nil { + t.Fatalf("read %s: %v", realworldRoot, err) + } + // After every statement has been parsed, formatted and re-parsed, the + // shared read-only stand-ins returned by the DialectExtras/DialectModifiers + // accessors must still be empty: a write through one would leak into every + // AST that lacks its own extras. + known := loadKnownFailures(t) + seen := map[string]bool{} + var current []string // "key stage" for every failing statement + + for _, d := range dirs { + if !d.IsDir() { + continue + } + dialect := d.Name() + if !keywords.IsValidDialect(dialect) { + t.Errorf("testdata/realworld/%s: not a valid dialect name", dialect) + continue + } + files, _ := filepath.Glob(filepath.Join(realworldRoot, dialect, "*.sql")) + for _, file := range files { + data, err := os.ReadFile(file) + if err != nil { + t.Fatalf("read %s: %v", file, err) + } + base := dialect + "/" + filepath.Base(file) + for i, stmt := range SplitStatements(string(data), dialect) { + if onlyComments(stmt) { + continue + } + key := fmt.Sprintf("%s#%d", base, i) + seen[key] = true + stage, detail := realworldCheck(stmt, keywords.SQLDialect(dialect)) + if stage != "ok" { + current = append(current, key+" "+stage) + } + if *updateKnownFailures { + continue + } + want, isKnown := known[key] + switch { + case stage == "ok" && isKnown: + t.Errorf("%s now passes; remove it from known_failures.txt (was %q)", key, want) + case stage != "ok" && !isKnown: + t.Errorf("%s failed at %s: %s\n sql: %s", key, stage, detail, compact(stmt)) + case stage != "ok" && stage != want: + t.Errorf("%s failure stage changed %s → %s: %s", key, want, stage, detail) + } + } + } + } + + if *updateKnownFailures { + writeKnownFailures(t, current) + return + } + + var stale []string + for key := range known { + if !seen[key] { + stale = append(stale, key) + } + } + sort.Strings(stale) + for _, key := range stale { + t.Errorf("known_failures.txt lists %s, which no longer exists", key) + } +} + +func realworldCheck(stmt string, d keywords.SQLDialect) (stage, detail string) { + defer func() { + if p := recover(); p != nil { + stage, detail = "panic", fmt.Sprint(p) + } + }() + tree, err := ParseWithDialect(stmt, d) + if err != nil { + return "parse", err.Error() + } + f1 := FormatAST(tree) + tree2, err := ParseWithDialect(f1, d) + if err != nil { + return "reparse", err.Error() + "\n formatted: " + compact(f1) + } + if f2 := FormatAST(tree2); f2 != f1 { + return "idempotent", compact(f1) + "\n != " + compact(f2) + } + if lost := dropcheck.DroppedWords(stmt, f1); len(lost) > 0 { + return "drop", "formatter lost " + strings.Join(lost, " ") + "\n formatted: " + compact(f1) + } + return "ok", "" +} + +func writeKnownFailures(t *testing.T, lines []string) { + t.Helper() + sort.Strings(lines) + header := "# Real-world corpus statements that GoSQLX does not yet handle.\n" + + "# Format: /# \n" + + "# Stages: parse, reparse, idempotent, drop (formatter lost words), panic.\n" + + "# Regenerate: go test ./pkg/gosqlx -run TestRealWorldCorpus -update-known-failures\n" + data := header + strings.Join(lines, "\n") + "\n" + if err := os.WriteFile(filepath.Join(realworldRoot, "known_failures.txt"), []byte(data), 0o644); err != nil { + t.Fatalf("write known_failures.txt: %v", err) + } + t.Logf("wrote %d known failures", len(lines)) +} + +// loadKnownFailures reads "dialect/file.sql#N stage # reason" lines. +func loadKnownFailures(t *testing.T) map[string]string { + t.Helper() + known := map[string]string{} + f, err := os.Open(filepath.Join(realworldRoot, "known_failures.txt")) + if os.IsNotExist(err) { + return known + } + if err != nil { + t.Fatalf("open known_failures.txt: %v", err) + } + defer f.Close() + sc := bufio.NewScanner(f) + for sc.Scan() { + line := sc.Text() + if i := strings.IndexByte(line, '#'); i == 0 { + continue + } + if i := strings.Index(line, " #"); i >= 0 { + line = line[:i] + } + fields := strings.Fields(line) + if len(fields) != 2 { + continue + } + known[fields[0]] = fields[1] + } + if err := sc.Err(); err != nil { + t.Fatalf("scan known_failures.txt: %v", err) + } + return known +} + +func onlyComments(s string) bool { + for _, line := range strings.Split(s, "\n") { + if t := strings.TrimSpace(line); t != "" && !strings.HasPrefix(t, "--") { + return false + } + } + return true +} + +func compact(s string) string { + s = strings.Join(strings.Fields(s), " ") + if len(s) > 240 { + s = s[:240] + "…" + } + return s +} diff --git a/scripts/dialect-audit/main.go b/scripts/dialect-audit/main.go new file mode 100644 index 00000000..12a05d81 --- /dev/null +++ b/scripts/dialect-audit/main.go @@ -0,0 +1,143 @@ +// Copyright 2026 GoSQLX Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Command dialect-audit runs real-world SQL files through the user-facing +// pipeline (split → parse with dialect → format → reparse → reformat) and +// reports every statement that fails a stage. +// +// Usage: +// +// go run ./scripts/dialect-audit -dialect postgresql testdata/realworld/postgresql/*.sql +// go run ./scripts/dialect-audit -dialect mysql -json file.sql +package main + +import ( + "encoding/json" + "flag" + "fmt" + "os" + "strings" + + "github.com/ajitpratap0/GoSQLX/internal/dropcheck" + "github.com/ajitpratap0/GoSQLX/pkg/gosqlx" + "github.com/ajitpratap0/GoSQLX/pkg/sql/keywords" +) + +type result struct { + File string `json:"file"` + Index int `json:"index"` + Stage string `json:"stage"` // parse | reparse | idempotent | drop | panic | ok + SQL string `json:"sql"` + Error string `json:"error,omitempty"` + Format string `json:"formatted,omitempty"` +} + +func main() { + dialect := flag.String("dialect", "generic", "SQL dialect") + asJSON := flag.Bool("json", false, "emit JSON lines for every failure") + verbose := flag.Bool("v", false, "print passing statements too") + flag.Parse() + + d := keywords.SQLDialect(*dialect) + total, failed := 0, 0 + stages := map[string]int{} + enc := json.NewEncoder(os.Stdout) + + for _, path := range flag.Args() { + data, err := os.ReadFile(path) + if err != nil { + fmt.Fprintf(os.Stderr, "read %s: %v\n", path, err) + os.Exit(2) + } + for i, stmt := range gosqlx.SplitStatements(string(data), *dialect) { + if strings.TrimSpace(stripComments(stmt)) == "" { + continue + } + total++ + r := check(path, i, stmt, d) + stages[r.Stage]++ + if r.Stage == "ok" { + if *verbose { + fmt.Printf("ok %s#%d\n", path, i) + } + continue + } + failed++ + if *asJSON { + _ = enc.Encode(r) + continue + } + fmt.Printf("FAIL %s#%d [%s] %s\n sql: %s\n", path, i, r.Stage, r.Error, oneLine(r.SQL)) + if r.Format != "" { + fmt.Printf(" fmt: %s\n", oneLine(r.Format)) + } + } + } + fmt.Fprintf(os.Stderr, "dialect=%s total=%d passed=%d failed=%d stages=%v\n", + *dialect, total, total-failed, failed, stages) + if failed > 0 { + os.Exit(1) + } +} + +func check(path string, i int, stmt string, d keywords.SQLDialect) (r result) { + r = result{File: path, Index: i, SQL: stmt} + defer func() { + if p := recover(); p != nil { + r.Stage, r.Error = "panic", fmt.Sprint(p) + } + }() + tree, err := gosqlx.ParseWithDialect(stmt, d) + if err != nil { + r.Stage, r.Error = "parse", err.Error() + return r + } + f1 := gosqlx.FormatAST(tree) + r.Format = f1 + tree2, err := gosqlx.ParseWithDialect(f1, d) + if err != nil { + r.Stage, r.Error = "reparse", err.Error() + return r + } + if f2 := gosqlx.FormatAST(tree2); f2 != f1 { + r.Stage, r.Error = "idempotent", "format(parse(format(x))) != format(x): "+oneLine(f2) + return r + } + if lost := dropcheck.DroppedWords(stmt, f1); len(lost) > 0 { + r.Stage, r.Error = "drop", "formatter output lost words: "+strings.Join(lost, " ") + return r + } + r.Stage, r.Format = "ok", "" + return r +} + +func stripComments(s string) string { + var b strings.Builder + for _, line := range strings.Split(s, "\n") { + if t := strings.TrimSpace(line); strings.HasPrefix(t, "--") { + continue + } + b.WriteString(line) + b.WriteByte('\n') + } + return b.String() +} + +func oneLine(s string) string { + s = strings.Join(strings.Fields(s), " ") + if len(s) > 300 { + s = s[:300] + "…" + } + return s +} diff --git a/testdata/realworld/clickhouse/01_app_crud.sql b/testdata/realworld/clickhouse/01_app_crud.sql new file mode 100644 index 00000000..aa41bb47 --- /dev/null +++ b/testdata/realworld/clickhouse/01_app_crud.sql @@ -0,0 +1,26 @@ +-- ORM / application-style CRUD against ClickHouse tables (users, orders, sessions) +SELECT id, email, created_at FROM users WHERE id = 42; +SELECT id, email FROM users WHERE email = 'a@example.com' LIMIT 1; +SELECT * FROM users WHERE id IN (1, 2, 3) ORDER BY id; +SELECT id, name FROM users WHERE name LIKE 'Jo%' AND is_active = 1 ORDER BY created_at DESC LIMIT 20 OFFSET 40; +SELECT count() FROM users; +SELECT count(*) AS total, countIf(is_active = 1) AS active FROM users; +INSERT INTO users (id, email, name, created_at) VALUES (1, 'a@example.com', 'Alice', now()); +INSERT INTO users (id, email, name) VALUES (2, 'b@example.com', 'Bob'), (3, 'c@example.com', 'Carol'); +INSERT INTO users_archive SELECT * FROM users WHERE created_at < '2020-01-01'; +INSERT INTO events (ts, user_id, name) SELECT now(), id, 'migrated' FROM users WHERE is_active = 0; +ALTER TABLE users UPDATE name = 'Alicia' WHERE id = 1; +ALTER TABLE users UPDATE is_active = 0, updated_at = now() WHERE last_login < now() - INTERVAL 1 YEAR; +ALTER TABLE users DELETE WHERE id = 1; +ALTER TABLE events DELETE WHERE ts < now() - INTERVAL 90 DAY; +DELETE FROM users WHERE id = 3; +TRUNCATE TABLE tmp_staging; +SELECT o.id, o.total, u.email FROM orders AS o INNER JOIN users AS u ON o.user_id = u.id WHERE o.status = 'paid' LIMIT 100; +SELECT u.id, count() AS cnt FROM users u LEFT JOIN orders o ON o.user_id = u.id GROUP BY u.id HAVING cnt > 5 ORDER BY cnt DESC; +SELECT id FROM orders WHERE user_id = {user_id:UInt64} AND created_at >= {since:DateTime}; +SELECT * FROM orders WHERE status NOT IN ('cancelled', 'refunded') AND total BETWEEN 10 AND 500; +SELECT DISTINCT country FROM users WHERE country != '' ORDER BY country; +SELECT id, if(is_active = 1, 'active', 'inactive') AS state FROM users; +SELECT id, multiIf(total < 10, 'small', total < 100, 'medium', 'large') AS bucket FROM orders; +SELECT id, CASE WHEN total > 100 THEN 'big' ELSE 'small' END AS size FROM orders; +SELECT id, coalesce(nickname, name, 'unknown') AS display FROM users; diff --git a/testdata/realworld/clickhouse/02_reporting.sql b/testdata/realworld/clickhouse/02_reporting.sql new file mode 100644 index 00000000..0e7c8e07 --- /dev/null +++ b/testdata/realworld/clickhouse/02_reporting.sql @@ -0,0 +1,32 @@ +-- Dashboard and BI reporting queries: daily revenue, funnels, top-N +SELECT toDate(created_at) AS day, sum(total) AS revenue, count() AS orders FROM orders GROUP BY day ORDER BY day; +SELECT toStartOfMonth(created_at) AS month, sum(total) AS revenue FROM orders WHERE created_at >= now() - INTERVAL 12 MONTH GROUP BY month ORDER BY month; +SELECT toStartOfHour(ts) AS hour, count() AS hits FROM web_events WHERE ts >= now() - INTERVAL 1 DAY GROUP BY hour ORDER BY hour; +SELECT toStartOfInterval(ts, INTERVAL 5 MINUTE) AS bucket, count() FROM web_events GROUP BY bucket ORDER BY bucket; +SELECT country, uniq(user_id) AS users, uniqExact(session_id) AS sessions FROM web_events GROUP BY country ORDER BY users DESC LIMIT 10; +SELECT quantile(0.9)(latency_ms) AS p90, quantile(0.99)(latency_ms) AS p99 FROM requests; +SELECT quantiles(0.5, 0.9, 0.99)(latency_ms) FROM requests WHERE ts > now() - INTERVAL 1 HOUR; +SELECT endpoint, quantileTDigest(0.95)(latency_ms) AS p95, avg(latency_ms), max(latency_ms) FROM requests GROUP BY endpoint ORDER BY p95 DESC; +SELECT sumIf(total, status = 'paid') AS paid, sumIf(total, status = 'refunded') AS refunded FROM orders; +SELECT countIf(status = 'error') / count() AS error_rate FROM requests; +SELECT avgIf(latency_ms, status < 500) FROM requests; +SELECT uniqIf(user_id, event = 'purchase') AS buyers, uniqIf(user_id, event = 'view') AS viewers FROM web_events; +SELECT category, argMax(name, price) AS priciest, argMin(name, price) AS cheapest FROM products GROUP BY category; +SELECT groupArray(name) AS names, groupUniqArray(category) FROM products; +SELECT user_id, topK(3)(page) AS top_pages FROM web_events GROUP BY user_id; +SELECT windowFunnel(3600)(ts, event = 'view', event = 'cart', event = 'purchase') AS level, count() FROM web_events GROUP BY user_id; +SELECT product_id, sum(qty) AS units FROM order_items GROUP BY product_id ORDER BY units DESC LIMIT 10; +SELECT date, sum(amount) FROM sales GROUP BY date WITH TOTALS ORDER BY date; +SELECT region, product, sum(amount) FROM sales GROUP BY region, product WITH ROLLUP; +SELECT region, product, sum(amount) FROM sales GROUP BY region, product WITH CUBE; +SELECT a, b, count() FROM t GROUP BY ROLLUP(a, b); +SELECT formatReadableSize(sum(bytes)) AS size, formatReadableQuantity(count()) FROM files; +SELECT round(avg(total), 2), round(stddevPop(total), 2), median(total) FROM orders; +SELECT toYYYYMM(created_at) AS ym, count() FROM orders GROUP BY ym ORDER BY ym DESC LIMIT 12; +SELECT dateDiff('day', min(created_at), max(created_at)) FROM orders; +SELECT toDate(ts) AS d, count() FROM web_events WHERE ts >= toStartOfDay(now()) - INTERVAL 7 DAY AND ts < toStartOfDay(now()) GROUP BY d; +SELECT d, c, sum(c) OVER (ORDER BY d) AS running FROM (SELECT toDate(ts) AS d, count() AS c FROM web_events GROUP BY d) ORDER BY d; +SELECT number FROM numbers(10); +SELECT toDate('2024-01-01') + number AS d FROM numbers(30); +SELECT 1 + 2 * 3 AS x, (1 + 2) * 3 AS y, 10 % 3 AS z, -5 AS neg; +SELECT arrayJoin([1, 2, 3]) AS n; diff --git a/testdata/realworld/clickhouse/03_ddl_migrations.sql b/testdata/realworld/clickhouse/03_ddl_migrations.sql new file mode 100644 index 00000000..eef952ad --- /dev/null +++ b/testdata/realworld/clickhouse/03_ddl_migrations.sql @@ -0,0 +1,48 @@ +-- Schema migrations: MergeTree family DDL, TTL, codecs, projections, indices +CREATE TABLE events (ts DateTime, user_id UInt64, name String) ENGINE = MergeTree ORDER BY (user_id, ts); +CREATE TABLE IF NOT EXISTS default.events2 (ts DateTime, user_id UInt64, name LowCardinality(String), props Map(String, String)) ENGINE = MergeTree PARTITION BY toYYYYMM(ts) ORDER BY (name, user_id, ts) SETTINGS index_granularity = 8192; +CREATE TABLE web_events (ts DateTime64(3) CODEC(Delta, ZSTD(1)), user_id UInt64, session_id UUID, url String, country LowCardinality(String), tags Array(String), amount Nullable(Decimal(18, 4)), ua Tuple(browser String, os String)) ENGINE = MergeTree PARTITION BY toDate(ts) ORDER BY (country, user_id, ts) TTL toDateTime(ts) + INTERVAL 90 DAY DELETE SETTINGS index_granularity = 8192; +CREATE TABLE metrics (ts DateTime, host LowCardinality(String), value Float64) ENGINE = MergeTree ORDER BY (host, ts) TTL ts + INTERVAL 1 MONTH TO VOLUME 'cold', ts + INTERVAL 1 YEAR DELETE; +CREATE TABLE users_rmt (id UInt64, name String, version UInt32) ENGINE = ReplacingMergeTree(version) ORDER BY id; +CREATE TABLE daily_sum (day Date, key String, total UInt64) ENGINE = SummingMergeTree ORDER BY (day, key); +CREATE TABLE daily_agg (day Date, key String, uniq_state AggregateFunction(uniq, UInt64), q_state AggregateFunction(quantile(0.9), Float64)) ENGINE = AggregatingMergeTree ORDER BY (day, key); +CREATE TABLE collapsing (id UInt64, sign Int8, v UInt32) ENGINE = CollapsingMergeTree(sign) ORDER BY id; +CREATE TABLE repl_events (ts DateTime, id UInt64) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/repl_events', '{replica}') ORDER BY (id, ts); +CREATE TABLE dist_events AS local_events ENGINE = Distributed('my_cluster', 'default', 'local_events', rand()); +CREATE TABLE IF NOT EXISTS lc_test (id UInt32, s LowCardinality(Nullable(String))) ENGINE = MergeTree ORDER BY id; +CREATE TABLE t_enum (id UInt32, status Enum8('new' = 1, 'paid' = 2, 'shipped' = 3)) ENGINE = MergeTree ORDER BY id; +CREATE TABLE t_defaults (id UInt64, created DateTime DEFAULT now(), day Date MATERIALIZED toDate(created), note String DEFAULT '' COMMENT 'free text', hash UInt64 ALIAS cityHash64(id)) ENGINE = MergeTree ORDER BY id; +CREATE TABLE t_idx (id UInt64, s String, INDEX idx_s s TYPE tokenbf_v1(10240, 3, 0) GRANULARITY 4, INDEX idx_id id TYPE minmax GRANULARITY 1) ENGINE = MergeTree ORDER BY id; +CREATE TABLE t_nested (id UInt64, n Nested(k String, v UInt32)) ENGINE = MergeTree ORDER BY id; +CREATE TABLE t_log (msg String) ENGINE = Log; +CREATE TABLE t_mem (a UInt8) ENGINE = Memory; +CREATE TABLE t_sample (id UInt64, h UInt64) ENGINE = MergeTree ORDER BY (id, intHash32(h)) SAMPLE BY intHash32(h); +CREATE TABLE copy_of_events AS events; +CREATE TABLE t_from_select ENGINE = MergeTree ORDER BY id AS SELECT id, name FROM users; +CREATE DATABASE IF NOT EXISTS analytics; +CREATE DATABASE analytics ENGINE = Atomic; +DROP TABLE IF EXISTS tmp_staging; +DROP DATABASE IF EXISTS scratch; +RENAME TABLE old_events TO events_old, new_events TO old_events; +EXCHANGE TABLES a AND b; +ALTER TABLE events ADD COLUMN IF NOT EXISTS country LowCardinality(String) DEFAULT '' AFTER user_id; +ALTER TABLE events DROP COLUMN IF EXISTS legacy; +ALTER TABLE events MODIFY COLUMN name LowCardinality(String); +ALTER TABLE events RENAME COLUMN name TO event_name; +ALTER TABLE events COMMENT COLUMN event_name 'event type'; +ALTER TABLE events MODIFY TTL ts + INTERVAL 6 MONTH; +ALTER TABLE events ADD INDEX idx_name event_name TYPE bloom_filter GRANULARITY 3; +ALTER TABLE events DROP INDEX idx_name; +ALTER TABLE events MATERIALIZE INDEX idx_name; +ALTER TABLE events ADD PROJECTION p_by_user (SELECT user_id, count() GROUP BY user_id); +ALTER TABLE events MODIFY SETTING ttl_only_drop_parts = 1; +ALTER TABLE events DROP PARTITION '202401'; +ALTER TABLE events DROP PARTITION ID '20240101'; +ALTER TABLE events DETACH PARTITION 202301; +ALTER TABLE events ATTACH PARTITION 202301; +ALTER TABLE events FREEZE PARTITION '2024-01-01'; +ALTER TABLE events ON CLUSTER my_cluster ADD COLUMN x UInt8; +CREATE TABLE ddl_cluster ON CLUSTER my_cluster (id UInt64) ENGINE = MergeTree ORDER BY id; +OPTIMIZE TABLE events FINAL; +OPTIMIZE TABLE events PARTITION 202401 FINAL; +OPTIMIZE TABLE events DEDUPLICATE; diff --git a/testdata/realworld/clickhouse/04_window_analytics.sql b/testdata/realworld/clickhouse/04_window_analytics.sql new file mode 100644 index 00000000..d408a4e0 --- /dev/null +++ b/testdata/realworld/clickhouse/04_window_analytics.sql @@ -0,0 +1,32 @@ +-- Window functions, CTE chains, retention and cohort analytics +SELECT user_id, ts, row_number() OVER (PARTITION BY user_id ORDER BY ts) AS rn FROM web_events; +SELECT user_id, ts, lagInFrame(ts) OVER (PARTITION BY user_id ORDER BY ts ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS prev_ts FROM web_events; +SELECT user_id, ts, lag(ts, 1) OVER (PARTITION BY user_id ORDER BY ts) AS prev FROM web_events; +SELECT day, revenue, avg(revenue) OVER (ORDER BY day ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7 FROM daily_rev; +SELECT day, revenue, sum(revenue) OVER (ORDER BY day RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cum FROM daily_rev; +SELECT user_id, amount, rank() OVER w AS r, dense_rank() OVER w AS dr FROM orders WINDOW w AS (PARTITION BY user_id ORDER BY amount DESC); +SELECT id, ntile(4) OVER (ORDER BY score) AS quartile, first_value(score) OVER (ORDER BY score) AS mn, last_value(score) OVER (ORDER BY score ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS mx FROM scores; +SELECT id, percent_rank() OVER (ORDER BY score), cume_dist() OVER (ORDER BY score) FROM scores; +WITH daily AS (SELECT toDate(ts) AS d, count() AS c FROM web_events GROUP BY d) SELECT d, c FROM daily ORDER BY d; +WITH 7 AS days SELECT count() FROM web_events WHERE ts >= now() - INTERVAL days DAY; +WITH (SELECT max(ts) FROM web_events) AS latest SELECT count() FROM web_events WHERE ts = latest; +WITH first_seen AS (SELECT user_id, min(toDate(ts)) AS cohort FROM web_events GROUP BY user_id), activity AS (SELECT DISTINCT user_id, toDate(ts) AS d FROM web_events) SELECT f.cohort, dateDiff('day', f.cohort, a.d) AS age, uniq(a.user_id) AS users FROM first_seen AS f INNER JOIN activity AS a ON a.user_id = f.user_id GROUP BY f.cohort, age ORDER BY f.cohort, age; +WITH a AS (SELECT 1 AS x), b AS (SELECT x + 1 AS y FROM a), c AS (SELECT y * 2 AS z FROM b), d AS (SELECT z + 10 AS w FROM c) SELECT * FROM d; +WITH RECURSIVE t AS (SELECT 1 AS n UNION ALL SELECT n + 1 FROM t WHERE n < 5) SELECT * FROM t; +SELECT retention(ts = today(), ts = today() - 1, ts = today() - 7) FROM web_events GROUP BY user_id; +SELECT sequenceMatch('(?1)(?2)')(ts, event = 'a', event = 'b') FROM web_events GROUP BY user_id; +SELECT user_id, sum(amount) OVER (PARTITION BY user_id ORDER BY ts) / sum(amount) OVER (PARTITION BY user_id) AS frac FROM orders; +SELECT * FROM (SELECT user_id, row_number() OVER (PARTITION BY user_id ORDER BY ts DESC) AS rn FROM web_events) WHERE rn = 1; +SELECT x FROM (SELECT * FROM (SELECT * FROM (SELECT number AS x FROM numbers(100)) WHERE x > 10) WHERE x < 90) WHERE x % 2 = 0; +SELECT a.x, b.y, c.z, d.w, e.v FROM a INNER JOIN b ON a.id = b.id LEFT JOIN c ON b.id = c.id LEFT JOIN d ON c.id = d.id ANY LEFT JOIN e ON d.id = e.id; +SELECT * FROM a ASOF JOIN b ON a.id = b.id AND a.ts >= b.ts; +SELECT * FROM a GLOBAL ANY LEFT JOIN b USING (id); +SELECT * FROM a INNER JOIN b USING (id, ts); +SELECT * FROM a CROSS JOIN b; +SELECT * FROM a FULL OUTER JOIN b ON a.id = b.id; +SELECT * FROM a LEFT SEMI JOIN b ON a.id = b.id; +SELECT * FROM a LEFT ANTI JOIN b ON a.id = b.id; +SELECT id FROM a UNION ALL SELECT id FROM b UNION ALL SELECT id FROM c; +SELECT id FROM a UNION DISTINCT SELECT id FROM b; +SELECT id FROM a INTERSECT SELECT id FROM b; +SELECT id FROM a EXCEPT SELECT id FROM b; diff --git a/testdata/realworld/clickhouse/05_dialect_specific.sql b/testdata/realworld/clickhouse/05_dialect_specific.sql new file mode 100644 index 00000000..1403dd5a --- /dev/null +++ b/testdata/realworld/clickhouse/05_dialect_specific.sql @@ -0,0 +1,73 @@ +-- ClickHouse-specific syntax: FINAL, SAMPLE, PREWHERE, ARRAY JOIN, LIMIT BY, WITH FILL, lambdas, FORMAT, SETTINGS +SELECT * FROM users FINAL WHERE id = 1; +SELECT count() FROM users FINAL; +SELECT * FROM events SAMPLE 0.1; +SELECT count() * 10 FROM events SAMPLE 1 / 10; +SELECT * FROM events SAMPLE 10000 OFFSET 0.5; +SELECT * FROM events PREWHERE user_id = 5 WHERE ts > now() - INTERVAL 1 DAY; +SELECT * FROM events FINAL PREWHERE user_id = 5 WHERE name = 'click'; +SELECT id, tag FROM t ARRAY JOIN tags AS tag; +SELECT id, tag FROM t LEFT ARRAY JOIN tags AS tag; +SELECT id, tag, idx FROM t ARRAY JOIN tags AS tag, arrayEnumerate(tags) AS idx; +SELECT n.k, n.v FROM t ARRAY JOIN n; +SELECT user_id, page FROM web_events ORDER BY ts DESC LIMIT 3 BY user_id; +SELECT user_id, page FROM web_events ORDER BY ts DESC LIMIT 1, 2 BY user_id; +SELECT user_id, page FROM web_events ORDER BY ts DESC LIMIT 2 BY user_id, page LIMIT 100; +SELECT n FROM numbers(10) ORDER BY n WITH FILL FROM 0 TO 20; +SELECT d, c FROM daily ORDER BY d WITH FILL FROM toDate('2024-01-01') TO toDate('2024-02-01') STEP 1; +SELECT d, c FROM daily ORDER BY d WITH FILL STEP INTERVAL 1 DAY; +SELECT d, c FROM daily ORDER BY d WITH FILL STEP toIntervalDay(1) INTERPOLATE (c AS c + 1); +SELECT arrayMap(x -> x * 2, [1, 2, 3]) AS doubled; +SELECT arrayFilter(x -> x > 1, arr) FROM t; +SELECT arrayMap((x, y) -> x + y, a, b) FROM t; +SELECT arrayExists(x -> x LIKE 'err%', tags) FROM t; +SELECT arrayFold((acc, x) -> acc + x, [1, 2, 3], toUInt64(0)); +SELECT arraySort(x -> -x, arr), arrayReverseSort(arr), arrayDistinct(arr) FROM t; +SELECT arrayStringConcat(arrayMap(x -> toString(x), arr), ',') FROM t; +SELECT has(tags, 'a'), hasAll(tags, ['a', 'b']), length(tags), empty(tags), arrayElement(tags, 1), tags[1] FROM t; +SELECT [1, 2, 3][2], (1, 'a').1, tupleElement((1, 'a'), 2); +SELECT m['key'], mapKeys(m), mapValues(m), map('a', 1, 'b', 2) FROM t; +SELECT sumState(x), uniqState(user_id), quantileState(0.9)(x) FROM t; +SELECT sumMerge(s), uniqMerge(u), quantileMerge(0.9)(q) FROM daily_agg GROUP BY day; +SELECT sumIfState(x, x > 0) FROM t; +SELECT sumArray(arr), avgArray(arr), minMap(m) FROM t; +SELECT sumForEach(arr) FROM t; +SELECT count() FROM t SETTINGS max_threads = 4; +SELECT * FROM t LIMIT 10 SETTINGS max_execution_time = 10, max_memory_usage = 10000000000; +SELECT * FROM t FORMAT JSON; +SELECT * FROM t FORMAT JSONEachRow; +SELECT * FROM t LIMIT 5 FORMAT TabSeparated; +SELECT * FROM t SETTINGS max_threads = 2 FORMAT CSVWithNames; +SELECT id FROM t WHERE id GLOBAL IN (SELECT id FROM dist_t); +SELECT id FROM t WHERE id GLOBAL NOT IN (SELECT id FROM dist_t); +SELECT * FROM t WHERE (a, b) IN ((1, 2), (3, 4)); +SELECT * FROM t WHERE id IN (SELECT id FROM s); +SELECT * FROM remote('host:9000', db.tbl) LIMIT 1; +SELECT * FROM cluster('my_cluster', default.events) LIMIT 1; +SELECT * FROM clusterAllReplicas('my_cluster', system.metrics) LIMIT 1; +SELECT * FROM url('https://example.com/data.csv', CSV, 'a UInt32, b String'); +SELECT * FROM s3('https://bucket.s3.amazonaws.com/data/*.parquet', 'Parquet') LIMIT 10; +SELECT * FROM file('data.csv', 'CSVWithNames'); +SELECT * FROM generateRandom('a UInt8, b String', 1, 10, 2) LIMIT 3; +SELECT cast(x AS String), CAST(x, 'UInt32'), x::UInt64, toInt32OrNull('12'), toFloat64OrZero('x'), accurateCastOrNull(x, 'UInt8') FROM t; +SELECT toDateTime('2024-01-01 00:00:00', 'UTC'), toTimeZone(now(), 'Asia/Kolkata'), now64(3), today(), yesterday(); +SELECT parseDateTimeBestEffort('2024-01-01T10:00:00Z'), formatDateTime(now(), '%Y-%m-%d'); +SELECT JSONExtractString(j, 'a'), JSONExtractInt(j, 'b', 'c'), JSONExtract(j, 'x', 'Array(UInt8)'), JSONHas(j, 'k'), simpleJSONExtractString(j, 'z') FROM t; +SELECT visitParamExtractString(j, 'a'), 1 FROM t; +SELECT extract(s, '(\\d+)'), match(s, '^a.*b$'), replaceRegexpAll(s, '[aeiou]', '_'), splitByChar(',', s), position(s, 'x'), lower(s), upper(s), trim(s), concat(a, b), substring(s, 1, 3) FROM t; +SELECT domain(url), path(url), protocol(url), extractURLParameter(url, 'q'), cutQueryString(url) FROM t; +SELECT cityHash64(s), sipHash64(s), MD5(s), SHA256(s), halfMD4(s), xxHash64(s) FROM t; +SELECT IPv4StringToNum('1.2.3.4'), IPv4NumToString(x), toIPv4('1.2.3.4'), toIPv6('::1'), isIPAddressInRange('1.2.3.4', '1.2.3.0/24'); +SELECT bitAnd(a, b), bitShiftLeft(a, 2), intDiv(a, b), modulo(a, b), a <> b; +SELECT x IS NULL, x IS NOT NULL, ifNull(x, 0), nullIf(x, 0), isNull(x), assumeNotNull(x) FROM t; +SELECT s LIKE '%a%', s NOT LIKE 'b%', s ILIKE '%A%', s NOT ILIKE 'x%' FROM t; +SELECT * FROM t WHERE ts > now() - INTERVAL 1 HOUR AND s != '' OR x BETWEEN 1 AND 2; +SELECT x AS "quoted col", `back-tick` FROM `my-table`; +SELECT * EXCEPT (secret) FROM t; +SELECT * EXCEPT (a, b), COLUMNS('^x_') FROM t; +SELECT COLUMNS('a|b') APPLY (sum) FROM t; +SELECT * REPLACE (x + 1 AS x) FROM t; +SELECT 'it''s', 'back\\slash', 'tab\t', 0x1F, 1e3, 1.5e-3; +SELECT toDecimal64(1.5, 2), 100500::Decimal(10, 2), toUInt256(1), 123456789012345678901234567890::UInt128; +SELECT 1 FROM system.one; +SELECT name, engine FROM system.tables WHERE database = currentDatabase(); diff --git a/testdata/realworld/clickhouse/06_materialized_views.sql b/testdata/realworld/clickhouse/06_materialized_views.sql new file mode 100644 index 00000000..932ebf51 --- /dev/null +++ b/testdata/realworld/clickhouse/06_materialized_views.sql @@ -0,0 +1,37 @@ +-- Materialized views, TO tables, aggregating pipelines, dictionaries, views +CREATE MATERIALIZED VIEW mv_daily TO daily_agg AS SELECT toDate(ts) AS day, key, uniqState(user_id) AS uniq_state FROM events GROUP BY day, key; +CREATE MATERIALIZED VIEW IF NOT EXISTS mv_hourly TO hourly_sum AS SELECT toStartOfHour(ts) AS hour, sum(value) AS total FROM metrics GROUP BY hour; +CREATE MATERIALIZED VIEW mv_engine ENGINE = SummingMergeTree ORDER BY (day, key) POPULATE AS SELECT toDate(ts) AS day, key, count() AS c FROM events GROUP BY day, key; +CREATE MATERIALIZED VIEW mv_engine2 ENGINE = AggregatingMergeTree PARTITION BY toYYYYMM(day) ORDER BY (day, key) AS SELECT toDate(ts) AS day, key, sumState(v) AS s FROM events GROUP BY day, key; +CREATE MATERIALIZED VIEW mv_cols TO dst (day Date, c UInt64) AS SELECT toDate(ts) AS day, count() AS c FROM src GROUP BY day; +CREATE MATERIALIZED VIEW mv_cluster ON CLUSTER my_cluster TO dst AS SELECT * FROM src; +CREATE MATERIALIZED VIEW kafka_mv TO events AS SELECT JSONExtractString(raw, 'id') AS id, now() AS ts FROM kafka_queue; +CREATE VIEW v_active_users AS SELECT id, email FROM users WHERE is_active = 1; +CREATE OR REPLACE VIEW v_recent AS SELECT * FROM events WHERE ts > now() - INTERVAL 1 DAY; +CREATE VIEW IF NOT EXISTS v_param AS SELECT * FROM events WHERE user_id = {uid:UInt64}; +CREATE TABLE kafka_queue (raw String) ENGINE = Kafka SETTINGS kafka_broker_list = 'kafka:9092', kafka_topic_list = 'events', kafka_group_name = 'ch', kafka_format = 'JSONAsString', kafka_num_consumers = 1; +CREATE TABLE s3_tbl (a UInt32, b String) ENGINE = S3('https://bucket.s3.amazonaws.com/data.csv', 'CSV'); +CREATE TABLE pg_tbl (id Int32, name String) ENGINE = PostgreSQL('pg:5432', 'db', 'tbl', 'user', 'pass'); +CREATE TABLE mysql_tbl (id Int32) ENGINE = MySQL('mysql:3306', 'db', 'tbl', 'user', 'pass'); +CREATE TABLE join_tbl (k UInt64, v String) ENGINE = Join(ANY, LEFT, k); +CREATE TABLE buf (a UInt8) ENGINE = Buffer(default, target, 16, 10, 100, 10000, 1000000, 10000000, 100000000); +CREATE TABLE merge_all (a UInt8) ENGINE = Merge(currentDatabase(), '^events_'); +CREATE DICTIONARY country_dict (id UInt64, name String) PRIMARY KEY id SOURCE(CLICKHOUSE(TABLE 'countries')) LIFETIME(MIN 300 MAX 360) LAYOUT(HASHED()); +CREATE DICTIONARY ip_trie (prefix String, asn UInt32) PRIMARY KEY prefix SOURCE(FILE(PATH '/data/ip.csv' FORMAT CSV)) LIFETIME(0) LAYOUT(IP_TRIE()); +SELECT dictGet('country_dict', 'name', toUInt64(country_id)) AS country FROM events; +SELECT dictGetOrDefault('country_dict', 'name', toUInt64(id), 'n/a') FROM events; +SELECT dictHas('country_dict', toUInt64(5)); +SYSTEM RELOAD DICTIONARY country_dict; +CREATE FUNCTION linear_equation AS (x, k, b) -> k * x + b; +CREATE FUNCTION IF NOT EXISTS plus_one AS x -> x + 1; +DROP FUNCTION linear_equation; +DROP VIEW IF EXISTS mv_daily; +DROP DICTIONARY IF EXISTS country_dict; +CREATE ROW POLICY pol ON events USING user_id = 1 TO ALL; +CREATE USER IF NOT EXISTS analyst IDENTIFIED WITH sha256_password BY 'secret'; +CREATE ROLE reader; +GRANT SELECT ON default.* TO reader; +GRANT reader TO analyst; +REVOKE SELECT ON default.events FROM analyst; +CREATE QUOTA q FOR INTERVAL 1 HOUR MAX queries = 100 TO analyst; +CREATE SETTINGS PROFILE p SETTINGS max_threads = 4 TO analyst; diff --git a/testdata/realworld/clickhouse/07_observability.sql b/testdata/realworld/clickhouse/07_observability.sql new file mode 100644 index 00000000..547896e7 --- /dev/null +++ b/testdata/realworld/clickhouse/07_observability.sql @@ -0,0 +1,47 @@ +-- Observability and admin: system tables, query logs, parts, merges +SELECT query_id, query, query_duration_ms, read_rows, memory_usage FROM system.query_log WHERE type = 'QueryFinish' AND event_time > now() - INTERVAL 1 HOUR ORDER BY query_duration_ms DESC LIMIT 20; +SELECT normalizedQueryHash(query) AS h, count() AS c, avg(query_duration_ms) AS avg_ms, quantile(0.95)(query_duration_ms) AS p95 FROM system.query_log WHERE event_date = today() AND type = 'QueryFinish' GROUP BY h ORDER BY c DESC LIMIT 20; +SELECT database, table, sum(rows) AS total_rows, formatReadableSize(sum(bytes_on_disk)) AS size, count() AS parts FROM system.parts WHERE active GROUP BY database, table ORDER BY sum(bytes_on_disk) DESC; +SELECT table, partition, name, rows, modification_time FROM system.parts WHERE active AND database = 'default' AND table = 'events' ORDER BY modification_time DESC LIMIT 10; +SELECT * FROM system.merges; +SELECT * FROM system.mutations WHERE NOT is_done; +SELECT * FROM system.replicas WHERE is_readonly OR absolute_delay > 60; +SELECT * FROM system.processes ORDER BY elapsed DESC; +SELECT metric, value FROM system.metrics WHERE metric LIKE 'Memory%'; +SELECT event, value FROM system.events WHERE event IN ('Query', 'SelectQuery', 'InsertQuery'); +SELECT name, value, changed FROM system.settings WHERE changed; +SELECT database, name, engine, total_rows, total_bytes FROM system.tables WHERE database NOT IN ('system', 'INFORMATION_SCHEMA', 'information_schema'); +SELECT database, table, name, type, compression_codec FROM system.columns WHERE database = 'default'; +SELECT hostName(), uptime(), version(), currentUser(), currentDatabase(); +SELECT toStartOfMinute(event_time) AS m, count() FROM system.query_log GROUP BY m ORDER BY m; +SELECT arrayJoin(stack_trace) FROM system.stack_trace LIMIT 5; +SELECT exception_code, any(exception) AS ex, count() FROM system.query_log WHERE exception_code != 0 GROUP BY exception_code ORDER BY count() DESC; +SELECT user, count() FROM system.query_log WHERE query_kind = 'Select' GROUP BY user; +SELECT query, ProfileEvents['SelectedRows'] AS rows_read, ProfileEvents['RealTimeMicroseconds'] AS rt FROM system.query_log WHERE type = 'QueryFinish' LIMIT 5; +SELECT tables, Settings['max_threads'] FROM system.query_log LIMIT 1; +SELECT disk_name, formatReadableSize(free_space), formatReadableSize(total_space) FROM system.disks; +SELECT * FROM system.asynchronous_metrics WHERE metric LIKE '%Memory%'; +SELECT * FROM system.clusters WHERE cluster = 'my_cluster'; +SELECT * FROM system.zookeeper WHERE path = '/clickhouse'; +SELECT * FROM system.part_log WHERE event_type = 'MergeParts' AND event_date = today(); +SHOW TABLES; +SHOW DATABASES; +SHOW TABLES FROM system LIKE 'query%'; +SHOW CREATE TABLE events; +DESCRIBE TABLE events; +DESC events; +EXISTS TABLE events; +USE analytics; +SET max_threads = 8; +SYSTEM FLUSH LOGS; +SYSTEM DROP DNS CACHE; +SYSTEM STOP MERGES events; +SYSTEM START MERGES events; +SYSTEM SYNC REPLICA events; +KILL QUERY WHERE query_id = 'abc' SYNC; +KILL MUTATION WHERE database = 'default' AND table = 'events'; +EXPLAIN SELECT * FROM events WHERE user_id = 1; +EXPLAIN PIPELINE SELECT count() FROM events; +EXPLAIN AST SELECT 1; +EXPLAIN SYNTAX SELECT 1 + 1; +EXPLAIN indexes = 1 SELECT * FROM events WHERE user_id = 1; diff --git a/testdata/realworld/clickhouse/08_etl_pipelines.sql b/testdata/realworld/clickhouse/08_etl_pipelines.sql new file mode 100644 index 00000000..3372333f --- /dev/null +++ b/testdata/realworld/clickhouse/08_etl_pipelines.sql @@ -0,0 +1,41 @@ +-- ETL / backfill / data-quality patterns +INSERT INTO events SELECT * FROM events_old WHERE ts >= '2024-01-01' AND ts < '2024-02-01'; +INSERT INTO daily_agg SELECT toDate(ts) AS day, key, uniqState(user_id), quantileState(0.9)(v) FROM events GROUP BY day, key; +INSERT INTO t (a, b) SETTINGS async_insert = 1, wait_for_async_insert = 0 VALUES (1, 'x'); +INSERT INTO FUNCTION s3('https://bucket.s3.amazonaws.com/out.parquet', 'Parquet') SELECT * FROM events; +INSERT INTO t SELECT * FROM file('in.csv', 'CSVWithNames'); +INSERT INTO t SELECT * FROM url('https://example.com/x.json', 'JSONEachRow'); +INSERT INTO t SELECT number, toString(number) FROM numbers(1000000); +SELECT count(), uniqExact(id), count() - uniqExact(id) AS dupes FROM events; +SELECT id, count() AS c FROM events GROUP BY id HAVING c > 1 ORDER BY c DESC LIMIT 100; +SELECT countIf(x IS NULL) AS null_cnt, countIf(x = '') AS empties, count() AS total FROM t; +SELECT min(ts), max(ts), dateDiff('hour', min(ts), max(ts)) FROM events; +SELECT partition, count() FROM system.parts WHERE table = 'events' AND active GROUP BY partition ORDER BY partition; +SELECT toDate(ts) AS d, count() FROM events GROUP BY d HAVING count() < 100 ORDER BY d; +SELECT * FROM events WHERE ts >= '2024-01-01 00:00:00' AND ts < '2024-01-02 00:00:00' AND NOT (user_id = 0 OR name = ''); +SELECT a.id FROM src AS a LEFT ANTI JOIN dst AS b ON a.id = b.id; +SELECT id FROM src WHERE id NOT IN (SELECT id FROM dst); +SELECT s.id, s.v FROM src AS s ANY LEFT JOIN dst AS d ON s.id = d.id WHERE d.id = 0; +SELECT arrayJoin(range(0, 10)) AS i; +SELECT ts, value, runningDifference(value) AS delta FROM (SELECT ts, value FROM metrics ORDER BY ts); +SELECT neighbor(value, -1) AS prev, value FROM metrics; +SELECT id, any(name), anyLast(name), min(ts), max(ts) FROM events GROUP BY id; +SELECT id, argMax(name, version) FROM users_rmt GROUP BY id; +SELECT tuple(a, b) AS pair, (a, b) AS pair2, tupleElement(pair, 1) FROM t; +SELECT t.1, t.2 FROM (SELECT (1, 'a') AS t); +SELECT toString(now(), 'UTC'), toUnixTimestamp(now()), fromUnixTimestamp(1700000000), toDate32('1990-01-01'); +SELECT count() FROM events WHERE has(tags, 'x') AND length(tags) > 2 AND arrayExists(t -> t = 'y', tags); +SELECT if(isNull(x), 0, x) + 1 FROM t; +SELECT a > 1 ? 'big' : 'small' FROM t; +SELECT intDiv(toUInt32(ts), 300) * 300 AS bucket, count() FROM events GROUP BY bucket; +SELECT lower(hex(MD5(concat(a, '|', b)))) AS k FROM t; +SELECT (SELECT count() FROM events) AS total, (SELECT max(ts) FROM events) AS latest; +SELECT e.id FROM events e WHERE EXISTS (SELECT 1 FROM users u WHERE u.id = e.user_id); +SELECT * FROM events WHERE user_id IN (SELECT id FROM users WHERE country IN ('US', 'CA')) AND ts > now() - INTERVAL 7 DAY ORDER BY ts DESC LIMIT 1000; +SELECT * FROM events e INNER JOIN (SELECT user_id, max(ts) AS ts FROM events GROUP BY user_id) m ON e.user_id = m.user_id AND e.ts = m.ts; +ALTER TABLE events REPLACE PARTITION '202401' FROM events_staging; +ALTER TABLE events MOVE PARTITION '202401' TO TABLE events_archive; +ALTER TABLE events UPDATE name = 'x' IN PARTITION '202401' WHERE id = 1; +ALTER TABLE events DELETE IN PARTITION '202401' WHERE ts < '2024-01-05'; +ALTER TABLE events CLEAR COLUMN legacy IN PARTITION '202401'; +OPTIMIZE TABLE events_staging FINAL; diff --git a/testdata/realworld/clickhouse/09_deep_nesting.sql b/testdata/realworld/clickhouse/09_deep_nesting.sql new file mode 100644 index 00000000..da92b92a --- /dev/null +++ b/testdata/realworld/clickhouse/09_deep_nesting.sql @@ -0,0 +1,38 @@ +-- Stress: deep nesting, long expressions, many CTEs, complex joins +SELECT ((((1 + 2) * (3 + 4)) - ((5 + 6) / (7 + 8))) + (((9)))) AS v; +SELECT if(a > 1, if(b > 2, if(c > 3, 'x', 'y'), 'z'), if(d > 4, 'w', 'v')) FROM t; +SELECT multiIf(a = 1, 'one', a = 2, 'two', a = 3, 'three', a = 4, 'four', a = 5, 'five', 'many') FROM t; +SELECT CASE a WHEN 1 THEN 'one' WHEN 2 THEN 'two' ELSE 'other' END, CASE WHEN a > 1 AND b < 2 OR c = 3 THEN 1 ELSE 0 END FROM t; +SELECT a AND b OR c AND NOT d, NOT a = b, a OR b AND c FROM t; +SELECT -a + b * c - d / e % f FROM t; +SELECT * FROM (SELECT * FROM (SELECT * FROM (SELECT * FROM (SELECT 1 AS x)))); +SELECT x FROM t WHERE x IN (SELECT y FROM u WHERE y IN (SELECT z FROM v WHERE z IN (SELECT w FROM w1))); +SELECT sum(x) FROM (SELECT a.x FROM a JOIN b ON a.k = b.k JOIN c ON b.k = c.k JOIN d ON c.k = d.k JOIN e ON d.k = e.k JOIN f ON e.k = f.k JOIN g ON f.k = g.k); +SELECT u.id, o.cnt, p.total, r.refunds FROM users AS u LEFT JOIN (SELECT user_id, count() AS cnt FROM orders GROUP BY user_id) AS o ON u.id = o.user_id LEFT JOIN (SELECT user_id, sum(amount) AS total FROM payments GROUP BY user_id) AS p ON u.id = p.user_id LEFT JOIN (SELECT user_id, sum(amount) AS refunds FROM refunds GROUP BY user_id) AS r ON u.id = r.user_id WHERE u.is_active = 1 ORDER BY p.total DESC LIMIT 50; +WITH base AS (SELECT * FROM events WHERE ts > now() - INTERVAL 1 DAY), by_user AS (SELECT user_id, count() AS c FROM base GROUP BY user_id), top AS (SELECT * FROM by_user ORDER BY c DESC LIMIT 100), joined AS (SELECT t.user_id, t.c, u.email FROM top AS t INNER JOIN users AS u ON t.user_id = u.id), fin AS (SELECT *, c * 2 AS c2 FROM joined) SELECT * FROM fin ORDER BY c DESC; +SELECT arrayMap(x -> arrayMap(y -> y * x, [1, 2, 3]), [1, 2, 3]); +SELECT arrayFilter(x -> x IN (SELECT id FROM t), arr) FROM s; +SELECT arrayMap(x -> if(x > 0, x, -x), arr), arrayMap(x -> (x, x * 2), arr) FROM t; +SELECT arrayMap(x -> x.1, arr) FROM t; +SELECT sumIf(a, b > 0 AND c IN (1, 2, 3)) / nullIf(countIf(d), 0) FROM t; +SELECT toStartOfInterval(ts, INTERVAL 15 MINUTE) AS b, groupArray((ts, v)) FROM t GROUP BY b; +SELECT a FROM t1 UNION ALL SELECT b FROM t2 UNION ALL (SELECT c FROM t3 ORDER BY c LIMIT 1) ORDER BY 1; +(SELECT 1) UNION ALL (SELECT 2); +SELECT * FROM (SELECT 1 AS a UNION ALL SELECT 2 AS a) AS u WHERE a > 1; +SELECT 1 AS a, 2 AS b ORDER BY a DESC, b ASC NULLS FIRST; +SELECT x FROM t ORDER BY x DESC NULLS LAST, y COLLATE 'en' LIMIT 5; +SELECT x FROM t ORDER BY x LIMIT 10 OFFSET 5; +SELECT x FROM t ORDER BY x OFFSET 5 ROWS FETCH FIRST 10 ROWS ONLY; +SELECT x FROM t ORDER BY x LIMIT 10 WITH TIES; +SELECT DISTINCT ON (a) a, b FROM t; +SELECT count(DISTINCT user_id), count(DISTINCT a, b), uniqCombined(a), uniqHLL12(a) FROM t; +SELECT a FROM t GROUP BY ALL; +SELECT a, b FROM t ORDER BY ALL; +SELECT a, count() FROM t GROUP BY 1 ORDER BY 2 DESC; +SELECT number AS n, n * 2 AS d, d + 1 AS e FROM numbers(3); +SELECT t.* FROM t; +SELECT db.t.col FROM db.t; +SELECT "a b" FROM "my table" AS "t 1"; +SELECT 1 /* inline */ + 2 -- trailing +; +SELECT '2024-01-01'::Date + INTERVAL 1 MONTH, now() - INTERVAL '1' DAY, INTERVAL 2 HOUR + INTERVAL 30 MINUTE; diff --git a/testdata/realworld/clickhouse/10_misc_real.sql b/testdata/realworld/clickhouse/10_misc_real.sql new file mode 100644 index 00000000..4d2c080b --- /dev/null +++ b/testdata/realworld/clickhouse/10_misc_real.sql @@ -0,0 +1,29 @@ +-- Miscellaneous real-world: Grafana, Superset, dbt-clickhouse, clickhouse-client generated +SELECT toStartOfInterval(toDateTime(ts), INTERVAL 60 second) AS t, avg(value) AS v FROM metrics WHERE ts >= toDateTime(1700000000) AND ts <= toDateTime(1700003600) GROUP BY t ORDER BY t ASC; +SELECT toUInt32(toStartOfInterval(ts, INTERVAL 1 MINUTE)) * 1000 AS time, count() AS value FROM web_events WHERE ts BETWEEN toDateTime(1700000000) AND toDateTime(1700003600) GROUP BY time ORDER BY time; +SELECT name FROM system.databases ORDER BY name; +SELECT database, name FROM system.tables WHERE engine NOT LIKE '%View' ORDER BY 1, 2; +SELECT * FROM events LIMIT 1 SETTINGS readonly = 1; +SELECT 'ok' FORMAT Null; +SELECT count() FROM events WHERE toDate(ts) = today() FORMAT TSV; +CREATE TABLE dbt_tmp__dbt_tmp (id UInt64, v String) ENGINE = MergeTree ORDER BY id SETTINGS replicated_deduplication_window = 0; +CREATE TABLE IF NOT EXISTS t_final (id UInt64, v String, ts DateTime) ENGINE = ReplacingMergeTree(ts) PRIMARY KEY id ORDER BY id; +CREATE TABLE t_primary (a UInt32, b UInt32) ENGINE = MergeTree PRIMARY KEY a ORDER BY (a, b); +CREATE TABLE t_comment (a UInt8) ENGINE = MergeTree ORDER BY a COMMENT 'a table'; +CREATE TABLE t_ttl_group (d Date, k UInt32, x UInt32, y UInt32) ENGINE = MergeTree ORDER BY (k, toStartOfMonth(d)) TTL d + INTERVAL 1 MONTH GROUP BY k, toStartOfMonth(d) SET x = max(x), y = min(y); +CREATE TABLE t_col_ttl (d DateTime, a Int32 TTL d + INTERVAL 1 DAY, b Int32) ENGINE = MergeTree ORDER BY d; +CREATE TABLE t_codec (a UInt64 CODEC(Delta(8), LZ4), b Float64 CODEC(Gorilla, ZSTD(3)), c String CODEC(ZSTD)) ENGINE = MergeTree ORDER BY a; +CREATE TABLE t_ipv (a IPv4, b IPv6, c UUID, d Date32, e DateTime('UTC'), f FixedString(16), g Bool, h Int128, i Float32) ENGINE = MergeTree ORDER BY a; +CREATE TABLE t_complex (a Array(Array(UInt8)), b Map(String, Array(UInt32)), c Tuple(UInt8, String), d Nullable(DateTime64(6, 'UTC')), e Array(Nullable(String)), f LowCardinality(Nullable(String)), g Map(LowCardinality(String), UInt64)) ENGINE = MergeTree ORDER BY tuple(); +CREATE TABLE t_agg (a SimpleAggregateFunction(sum, UInt64), b SimpleAggregateFunction(max, DateTime), c AggregateFunction(groupArray, String)) ENGINE = AggregatingMergeTree ORDER BY tuple(); +CREATE TEMPORARY TABLE tmp1 (a UInt8) ENGINE = Memory; +CREATE TEMPORARY TABLE tmp2 ENGINE = Memory AS SELECT 1 AS a; +CREATE TABLE t_settings (a UInt8) ENGINE = MergeTree ORDER BY a SETTINGS storage_policy = 'tiered', min_bytes_for_wide_part = 0, merge_with_ttl_timeout = 3600; +CREATE TABLE t_part_expr (ts DateTime, a UInt8) ENGINE = MergeTree PARTITION BY (toYYYYMM(ts), a % 4) ORDER BY ts; +CREATE TABLE t_orderby_expr (ts DateTime, u UInt64) ENGINE = MergeTree ORDER BY (toStartOfHour(ts), cityHash64(u) % 16, u); +CREATE TABLE t_ver (id UInt64, sign Int8, ver UInt32) ENGINE = VersionedCollapsingMergeTree(sign, ver) ORDER BY id; +CREATE TABLE t_cons (a UInt8, CONSTRAINT c1 CHECK a > 0, CONSTRAINT c2 ASSUME a < 100) ENGINE = MergeTree ORDER BY a; +CREATE TABLE t_cmt (a UInt8 COMMENT 'col a', b String CODEC(ZSTD) COMMENT 'col b') ENGINE = MergeTree ORDER BY a; +CREATE TABLE t_ro (a UInt8) ENGINE = MergeTree ORDER BY a SETTINGS index_granularity = 8192, index_granularity_bytes = 10485760; +CREATE TABLE t_sk (a UInt8, b String, INDEX i1 (a, b) TYPE set(100) GRANULARITY 2, INDEX i2 lower(b) TYPE ngrambf_v1(4, 1024, 1, 0) GRANULARITY 1) ENGINE = MergeTree ORDER BY a; +CREATE TABLE t_proj (a UInt8, b UInt8, PROJECTION p1 (SELECT a, sum(b) GROUP BY a)) ENGINE = MergeTree ORDER BY a; diff --git a/testdata/realworld/known_failures.txt b/testdata/realworld/known_failures.txt new file mode 100644 index 00000000..f277a179 --- /dev/null +++ b/testdata/realworld/known_failures.txt @@ -0,0 +1,1331 @@ +# Real-world corpus statements that GoSQLX does not yet handle. +# Format: /# +# Stages: parse, reparse, idempotent, drop (formatter lost words), panic. +# Regenerate: go test ./pkg/gosqlx -run TestRealWorldCorpus -update-known-failures +clickhouse/01_app_crud.sql#10 parse +clickhouse/01_app_crud.sql#11 parse +clickhouse/01_app_crud.sql#12 parse +clickhouse/01_app_crud.sql#13 parse +clickhouse/01_app_crud.sql#18 parse +clickhouse/02_reporting.sql#17 drop +clickhouse/02_reporting.sql#18 drop +clickhouse/02_reporting.sql#19 drop +clickhouse/02_reporting.sql#2 drop +clickhouse/02_reporting.sql#25 drop +clickhouse/02_reporting.sql#3 drop +clickhouse/02_reporting.sql#6 drop +clickhouse/03_ddl_migrations.sql#0 drop +clickhouse/03_ddl_migrations.sql#1 drop +clickhouse/03_ddl_migrations.sql#10 drop +clickhouse/03_ddl_migrations.sql#11 drop +clickhouse/03_ddl_migrations.sql#12 parse +clickhouse/03_ddl_migrations.sql#13 parse +clickhouse/03_ddl_migrations.sql#14 drop +clickhouse/03_ddl_migrations.sql#17 drop +clickhouse/03_ddl_migrations.sql#18 reparse +clickhouse/03_ddl_migrations.sql#19 parse +clickhouse/03_ddl_migrations.sql#2 drop +clickhouse/03_ddl_migrations.sql#20 parse +clickhouse/03_ddl_migrations.sql#21 parse +clickhouse/03_ddl_migrations.sql#23 parse +clickhouse/03_ddl_migrations.sql#24 parse +clickhouse/03_ddl_migrations.sql#25 parse +clickhouse/03_ddl_migrations.sql#26 parse +clickhouse/03_ddl_migrations.sql#27 parse +clickhouse/03_ddl_migrations.sql#28 parse +clickhouse/03_ddl_migrations.sql#3 drop +clickhouse/03_ddl_migrations.sql#30 parse +clickhouse/03_ddl_migrations.sql#31 parse +clickhouse/03_ddl_migrations.sql#32 parse +clickhouse/03_ddl_migrations.sql#33 parse +clickhouse/03_ddl_migrations.sql#34 parse +clickhouse/03_ddl_migrations.sql#35 parse +clickhouse/03_ddl_migrations.sql#36 parse +clickhouse/03_ddl_migrations.sql#37 parse +clickhouse/03_ddl_migrations.sql#38 parse +clickhouse/03_ddl_migrations.sql#39 parse +clickhouse/03_ddl_migrations.sql#4 drop +clickhouse/03_ddl_migrations.sql#40 parse +clickhouse/03_ddl_migrations.sql#41 parse +clickhouse/03_ddl_migrations.sql#42 parse +clickhouse/03_ddl_migrations.sql#43 parse +clickhouse/03_ddl_migrations.sql#44 parse +clickhouse/03_ddl_migrations.sql#45 parse +clickhouse/03_ddl_migrations.sql#46 parse +clickhouse/03_ddl_migrations.sql#5 drop +clickhouse/03_ddl_migrations.sql#6 drop +clickhouse/03_ddl_migrations.sql#7 drop +clickhouse/03_ddl_migrations.sql#8 drop +clickhouse/03_ddl_migrations.sql#9 reparse +clickhouse/04_window_analytics.sql#10 reparse +clickhouse/04_window_analytics.sql#19 drop +clickhouse/04_window_analytics.sql#21 drop +clickhouse/04_window_analytics.sql#25 parse +clickhouse/04_window_analytics.sql#26 parse +clickhouse/04_window_analytics.sql#28 parse +clickhouse/04_window_analytics.sql#9 parse +clickhouse/05_dialect_specific.sql#0 drop +clickhouse/05_dialect_specific.sql#1 drop +clickhouse/05_dialect_specific.sql#10 drop +clickhouse/05_dialect_specific.sql#13 parse +clickhouse/05_dialect_specific.sql#16 drop +clickhouse/05_dialect_specific.sql#17 parse +clickhouse/05_dialect_specific.sql#26 parse +clickhouse/05_dialect_specific.sql#35 parse +clickhouse/05_dialect_specific.sql#36 parse +clickhouse/05_dialect_specific.sql#37 parse +clickhouse/05_dialect_specific.sql#38 drop +clickhouse/05_dialect_specific.sql#39 drop +clickhouse/05_dialect_specific.sql#40 drop +clickhouse/05_dialect_specific.sql#5 drop +clickhouse/05_dialect_specific.sql#50 parse +clickhouse/05_dialect_specific.sql#55 parse +clickhouse/05_dialect_specific.sql#56 parse +clickhouse/05_dialect_specific.sql#6 drop +clickhouse/05_dialect_specific.sql#62 drop +clickhouse/05_dialect_specific.sql#64 parse +clickhouse/05_dialect_specific.sql#65 parse +clickhouse/05_dialect_specific.sql#66 parse +clickhouse/05_dialect_specific.sql#67 parse +clickhouse/05_dialect_specific.sql#68 idempotent +clickhouse/05_dialect_specific.sql#7 drop +clickhouse/05_dialect_specific.sql#8 drop +clickhouse/05_dialect_specific.sql#9 drop +clickhouse/06_materialized_views.sql#0 drop +clickhouse/06_materialized_views.sql#1 drop +clickhouse/06_materialized_views.sql#10 drop +clickhouse/06_materialized_views.sql#14 parse +clickhouse/06_materialized_views.sql#16 parse +clickhouse/06_materialized_views.sql#17 parse +clickhouse/06_materialized_views.sql#18 parse +clickhouse/06_materialized_views.sql#2 drop +clickhouse/06_materialized_views.sql#22 parse +clickhouse/06_materialized_views.sql#23 parse +clickhouse/06_materialized_views.sql#24 parse +clickhouse/06_materialized_views.sql#25 parse +clickhouse/06_materialized_views.sql#27 parse +clickhouse/06_materialized_views.sql#28 parse +clickhouse/06_materialized_views.sql#29 parse +clickhouse/06_materialized_views.sql#3 drop +clickhouse/06_materialized_views.sql#30 parse +clickhouse/06_materialized_views.sql#31 parse +clickhouse/06_materialized_views.sql#32 parse +clickhouse/06_materialized_views.sql#33 parse +clickhouse/06_materialized_views.sql#34 parse +clickhouse/06_materialized_views.sql#35 parse +clickhouse/06_materialized_views.sql#4 parse +clickhouse/06_materialized_views.sql#5 parse +clickhouse/06_materialized_views.sql#6 drop +clickhouse/06_materialized_views.sql#8 drop +clickhouse/06_materialized_views.sql#9 parse +clickhouse/07_observability.sql#0 drop +clickhouse/07_observability.sql#16 parse +clickhouse/07_observability.sql#27 parse +clickhouse/07_observability.sql#29 drop +clickhouse/07_observability.sql#30 drop +clickhouse/07_observability.sql#31 parse +clickhouse/07_observability.sql#32 parse +clickhouse/07_observability.sql#33 parse +clickhouse/07_observability.sql#34 parse +clickhouse/07_observability.sql#35 parse +clickhouse/07_observability.sql#36 parse +clickhouse/07_observability.sql#37 parse +clickhouse/07_observability.sql#38 parse +clickhouse/07_observability.sql#39 parse +clickhouse/07_observability.sql#40 parse +clickhouse/07_observability.sql#41 drop +clickhouse/07_observability.sql#42 drop +clickhouse/07_observability.sql#43 drop +clickhouse/07_observability.sql#44 drop +clickhouse/07_observability.sql#45 parse +clickhouse/08_etl_pipelines.sql#14 parse +clickhouse/08_etl_pipelines.sql#16 drop +clickhouse/08_etl_pipelines.sql#17 parse +clickhouse/08_etl_pipelines.sql#2 parse +clickhouse/08_etl_pipelines.sql#20 parse +clickhouse/08_etl_pipelines.sql#27 parse +clickhouse/08_etl_pipelines.sql#3 parse +clickhouse/08_etl_pipelines.sql#32 drop +clickhouse/08_etl_pipelines.sql#34 parse +clickhouse/08_etl_pipelines.sql#35 parse +clickhouse/08_etl_pipelines.sql#36 parse +clickhouse/08_etl_pipelines.sql#37 parse +clickhouse/08_etl_pipelines.sql#38 parse +clickhouse/08_etl_pipelines.sql#39 parse +clickhouse/09_deep_nesting.sql#10 drop +clickhouse/09_deep_nesting.sql#16 drop +clickhouse/09_deep_nesting.sql#17 parse +clickhouse/09_deep_nesting.sql#21 parse +clickhouse/09_deep_nesting.sql#24 parse +clickhouse/09_deep_nesting.sql#27 parse +clickhouse/09_deep_nesting.sql#28 parse +clickhouse/09_deep_nesting.sql#35 drop +clickhouse/10_misc_real.sql#0 drop +clickhouse/10_misc_real.sql#1 drop +clickhouse/10_misc_real.sql#10 drop +clickhouse/10_misc_real.sql#11 drop +clickhouse/10_misc_real.sql#12 parse +clickhouse/10_misc_real.sql#13 drop +clickhouse/10_misc_real.sql#14 drop +clickhouse/10_misc_real.sql#15 drop +clickhouse/10_misc_real.sql#16 drop +clickhouse/10_misc_real.sql#18 parse +clickhouse/10_misc_real.sql#19 drop +clickhouse/10_misc_real.sql#20 drop +clickhouse/10_misc_real.sql#21 drop +clickhouse/10_misc_real.sql#22 drop +clickhouse/10_misc_real.sql#23 parse +clickhouse/10_misc_real.sql#24 parse +clickhouse/10_misc_real.sql#25 drop +clickhouse/10_misc_real.sql#26 parse +clickhouse/10_misc_real.sql#27 drop +clickhouse/10_misc_real.sql#5 parse +clickhouse/10_misc_real.sql#6 parse +clickhouse/10_misc_real.sql#7 drop +clickhouse/10_misc_real.sql#8 drop +clickhouse/10_misc_real.sql#9 drop +mariadb/01_app_crud.sql#13 parse +mariadb/01_app_crud.sql#14 parse +mariadb/01_app_crud.sql#16 parse +mariadb/01_app_crud.sql#17 parse +mariadb/01_app_crud.sql#26 parse +mariadb/01_app_crud.sql#27 parse +mariadb/01_app_crud.sql#28 parse +mariadb/01_app_crud.sql#3 parse +mariadb/01_app_crud.sql#7 parse +mariadb/02_reporting.sql#1 drop +mariadb/02_reporting.sql#13 parse +mariadb/02_reporting.sql#17 parse +mariadb/02_reporting.sql#19 parse +mariadb/02_reporting.sql#20 parse +mariadb/02_reporting.sql#21 parse +mariadb/02_reporting.sql#25 drop +mariadb/02_reporting.sql#26 drop +mariadb/02_reporting.sql#27 parse +mariadb/02_reporting.sql#29 parse +mariadb/02_reporting.sql#3 drop +mariadb/02_reporting.sql#30 parse +mariadb/02_reporting.sql#31 parse +mariadb/02_reporting.sql#33 parse +mariadb/02_reporting.sql#38 parse +mariadb/02_reporting.sql#39 parse +mariadb/02_reporting.sql#40 parse +mariadb/02_reporting.sql#41 parse +mariadb/02_reporting.sql#43 drop +mariadb/02_reporting.sql#44 parse +mariadb/03_ddl_migrations.sql#0 parse +mariadb/03_ddl_migrations.sql#1 parse +mariadb/03_ddl_migrations.sql#10 drop +mariadb/03_ddl_migrations.sql#13 parse +mariadb/03_ddl_migrations.sql#14 parse +mariadb/03_ddl_migrations.sql#17 parse +mariadb/03_ddl_migrations.sql#18 parse +mariadb/03_ddl_migrations.sql#19 parse +mariadb/03_ddl_migrations.sql#2 parse +mariadb/03_ddl_migrations.sql#20 parse +mariadb/03_ddl_migrations.sql#21 parse +mariadb/03_ddl_migrations.sql#22 parse +mariadb/03_ddl_migrations.sql#23 parse +mariadb/03_ddl_migrations.sql#25 parse +mariadb/03_ddl_migrations.sql#26 parse +mariadb/03_ddl_migrations.sql#27 parse +mariadb/03_ddl_migrations.sql#29 parse +mariadb/03_ddl_migrations.sql#3 parse +mariadb/03_ddl_migrations.sql#31 parse +mariadb/03_ddl_migrations.sql#32 parse +mariadb/03_ddl_migrations.sql#36 parse +mariadb/03_ddl_migrations.sql#37 parse +mariadb/03_ddl_migrations.sql#38 parse +mariadb/03_ddl_migrations.sql#39 parse +mariadb/03_ddl_migrations.sql#4 parse +mariadb/03_ddl_migrations.sql#40 parse +mariadb/03_ddl_migrations.sql#41 parse +mariadb/03_ddl_migrations.sql#42 parse +mariadb/03_ddl_migrations.sql#43 parse +mariadb/03_ddl_migrations.sql#44 parse +mariadb/03_ddl_migrations.sql#45 parse +mariadb/03_ddl_migrations.sql#46 parse +mariadb/03_ddl_migrations.sql#47 parse +mariadb/03_ddl_migrations.sql#48 parse +mariadb/03_ddl_migrations.sql#49 parse +mariadb/03_ddl_migrations.sql#5 parse +mariadb/03_ddl_migrations.sql#50 parse +mariadb/03_ddl_migrations.sql#51 parse +mariadb/03_ddl_migrations.sql#52 parse +mariadb/03_ddl_migrations.sql#53 parse +mariadb/03_ddl_migrations.sql#6 parse +mariadb/03_ddl_migrations.sql#7 parse +mariadb/03_ddl_migrations.sql#8 reparse +mariadb/04_window_analytics.sql#14 drop +mariadb/04_window_analytics.sql#18 parse +mariadb/04_window_analytics.sql#4 drop +mariadb/04_window_analytics.sql#5 parse +mariadb/05_mariadb_specific.sql#11 parse +mariadb/05_mariadb_specific.sql#14 parse +mariadb/05_mariadb_specific.sql#17 parse +mariadb/05_mariadb_specific.sql#26 drop +mariadb/05_mariadb_specific.sql#27 drop +mariadb/05_mariadb_specific.sql#28 parse +mariadb/05_mariadb_specific.sql#29 parse +mariadb/05_mariadb_specific.sql#30 parse +mariadb/05_mariadb_specific.sql#31 parse +mariadb/05_mariadb_specific.sql#32 parse +mariadb/05_mariadb_specific.sql#33 parse +mariadb/05_mariadb_specific.sql#34 drop +mariadb/05_mariadb_specific.sql#35 drop +mariadb/05_mariadb_specific.sql#36 drop +mariadb/05_mariadb_specific.sql#37 drop +mariadb/05_mariadb_specific.sql#38 drop +mariadb/05_mariadb_specific.sql#39 parse +mariadb/05_mariadb_specific.sql#40 parse +mariadb/05_mariadb_specific.sql#41 parse +mariadb/05_mariadb_specific.sql#51 parse +mariadb/05_mariadb_specific.sql#53 parse +mariadb/05_mariadb_specific.sql#57 parse +mariadb/05_mariadb_specific.sql#58 parse +mariadb/05_mariadb_specific.sql#6 parse +mariadb/05_mariadb_specific.sql#63 parse +mariadb/05_mariadb_specific.sql#65 parse +mariadb/05_mariadb_specific.sql#66 parse +mariadb/05_mariadb_specific.sql#67 parse +mariadb/05_mariadb_specific.sql#68 parse +mariadb/05_mariadb_specific.sql#69 parse +mariadb/05_mariadb_specific.sql#9 parse +mariadb/06_etl_admin.sql#12 parse +mariadb/06_etl_admin.sql#13 parse +mariadb/06_etl_admin.sql#15 parse +mariadb/06_etl_admin.sql#16 parse +mariadb/06_etl_admin.sql#2 parse +mariadb/06_etl_admin.sql#22 parse +mariadb/06_etl_admin.sql#24 parse +mariadb/06_etl_admin.sql#25 parse +mariadb/06_etl_admin.sql#35 parse +mariadb/06_etl_admin.sql#36 parse +mariadb/06_etl_admin.sql#37 parse +mariadb/06_etl_admin.sql#38 parse +mariadb/06_etl_admin.sql#39 parse +mariadb/06_etl_admin.sql#40 parse +mariadb/06_etl_admin.sql#5 parse +mariadb/06_etl_admin.sql#6 parse +mariadb/06_etl_admin.sql#7 drop +mariadb/06_etl_admin.sql#8 parse +mariadb/06_etl_admin.sql#9 parse +mariadb/07_stored_programs.sql#0 parse +mariadb/07_stored_programs.sql#1 parse +mariadb/07_stored_programs.sql#10 parse +mariadb/07_stored_programs.sql#11 parse +mariadb/07_stored_programs.sql#12 parse +mariadb/07_stored_programs.sql#13 parse +mariadb/07_stored_programs.sql#14 parse +mariadb/07_stored_programs.sql#15 parse +mariadb/07_stored_programs.sql#16 parse +mariadb/07_stored_programs.sql#17 parse +mariadb/07_stored_programs.sql#18 parse +mariadb/07_stored_programs.sql#2 parse +mariadb/07_stored_programs.sql#3 parse +mariadb/07_stored_programs.sql#4 parse +mariadb/07_stored_programs.sql#5 parse +mariadb/07_stored_programs.sql#6 parse +mariadb/07_stored_programs.sql#7 parse +mariadb/07_stored_programs.sql#8 parse +mariadb/07_stored_programs.sql#9 parse +mariadb/08_complex_queries.sql#12 parse +mariadb/08_complex_queries.sql#18 drop +mariadb/08_complex_queries.sql#21 parse +mariadb/08_complex_queries.sql#26 reparse +mariadb/08_complex_queries.sql#27 reparse +mariadb/08_complex_queries.sql#29 parse +mariadb/08_complex_queries.sql#8 drop +mariadb/09_mysql_compat_misc.sql#0 parse +mariadb/09_mysql_compat_misc.sql#10 parse +mariadb/09_mysql_compat_misc.sql#11 parse +mariadb/09_mysql_compat_misc.sql#12 parse +mariadb/09_mysql_compat_misc.sql#14 parse +mariadb/09_mysql_compat_misc.sql#17 parse +mariadb/09_mysql_compat_misc.sql#25 parse +mariadb/09_mysql_compat_misc.sql#33 parse +mariadb/09_mysql_compat_misc.sql#36 parse +mariadb/09_mysql_compat_misc.sql#37 parse +mariadb/09_mysql_compat_misc.sql#39 parse +mariadb/09_mysql_compat_misc.sql#40 parse +mariadb/09_mysql_compat_misc.sql#41 parse +mariadb/09_mysql_compat_misc.sql#42 parse +mariadb/09_mysql_compat_misc.sql#46 parse +mariadb/09_mysql_compat_misc.sql#47 parse +mariadb/09_mysql_compat_misc.sql#49 parse +mariadb/09_mysql_compat_misc.sql#5 parse +mariadb/09_mysql_compat_misc.sql#50 parse +mariadb/09_mysql_compat_misc.sql#51 drop +mariadb/09_mysql_compat_misc.sql#52 drop +mariadb/09_mysql_compat_misc.sql#53 parse +mariadb/09_mysql_compat_misc.sql#54 parse +mariadb/09_mysql_compat_misc.sql#55 parse +mysql/01_app_crud.sql#11 parse +mysql/01_app_crud.sql#17 drop +mysql/01_app_crud.sql#18 parse +mysql/01_app_crud.sql#19 parse +mysql/01_app_crud.sql#21 parse +mysql/01_app_crud.sql#22 parse +mysql/01_app_crud.sql#23 parse +mysql/01_app_crud.sql#30 parse +mysql/01_app_crud.sql#6 parse +mysql/02_reporting.sql#1 drop +mysql/02_reporting.sql#22 drop +mysql/02_reporting.sql#23 drop +mysql/02_reporting.sql#24 drop +mysql/02_reporting.sql#28 parse +mysql/02_reporting.sql#30 parse +mysql/02_reporting.sql#31 parse +mysql/02_reporting.sql#32 parse +mysql/02_reporting.sql#34 parse +mysql/02_reporting.sql#5 drop +mysql/03_ddl_migrations.sql#0 parse +mysql/03_ddl_migrations.sql#1 parse +mysql/03_ddl_migrations.sql#10 parse +mysql/03_ddl_migrations.sql#11 parse +mysql/03_ddl_migrations.sql#12 parse +mysql/03_ddl_migrations.sql#14 parse +mysql/03_ddl_migrations.sql#15 parse +mysql/03_ddl_migrations.sql#16 parse +mysql/03_ddl_migrations.sql#17 parse +mysql/03_ddl_migrations.sql#18 parse +mysql/03_ddl_migrations.sql#20 parse +mysql/03_ddl_migrations.sql#21 parse +mysql/03_ddl_migrations.sql#22 parse +mysql/03_ddl_migrations.sql#23 parse +mysql/03_ddl_migrations.sql#25 parse +mysql/03_ddl_migrations.sql#26 parse +mysql/03_ddl_migrations.sql#27 parse +mysql/03_ddl_migrations.sql#3 parse +mysql/03_ddl_migrations.sql#30 parse +mysql/03_ddl_migrations.sql#32 parse +mysql/03_ddl_migrations.sql#33 parse +mysql/03_ddl_migrations.sql#37 parse +mysql/03_ddl_migrations.sql#38 parse +mysql/03_ddl_migrations.sql#4 parse +mysql/03_ddl_migrations.sql#42 parse +mysql/03_ddl_migrations.sql#43 parse +mysql/03_ddl_migrations.sql#6 parse +mysql/03_ddl_migrations.sql#7 reparse +mysql/03_ddl_migrations.sql#9 parse +mysql/04_window_analytics.sql#14 parse +mysql/04_window_analytics.sql#20 parse +mysql/04_window_analytics.sql#21 parse +mysql/04_window_analytics.sql#7 drop +mysql/04_window_analytics.sql#8 drop +mysql/05_dialect_specific.sql#10 reparse +mysql/05_dialect_specific.sql#11 reparse +mysql/05_dialect_specific.sql#12 parse +mysql/05_dialect_specific.sql#13 parse +mysql/05_dialect_specific.sql#14 parse +mysql/05_dialect_specific.sql#15 parse +mysql/05_dialect_specific.sql#18 parse +mysql/05_dialect_specific.sql#19 parse +mysql/05_dialect_specific.sql#20 parse +mysql/05_dialect_specific.sql#21 parse +mysql/05_dialect_specific.sql#22 parse +mysql/05_dialect_specific.sql#23 parse +mysql/05_dialect_specific.sql#24 parse +mysql/05_dialect_specific.sql#25 parse +mysql/05_dialect_specific.sql#26 parse +mysql/05_dialect_specific.sql#27 parse +mysql/05_dialect_specific.sql#28 parse +mysql/05_dialect_specific.sql#30 parse +mysql/05_dialect_specific.sql#35 parse +mysql/05_dialect_specific.sql#36 parse +mysql/05_dialect_specific.sql#38 parse +mysql/05_dialect_specific.sql#39 parse +mysql/05_dialect_specific.sql#41 parse +mysql/05_dialect_specific.sql#43 drop +mysql/05_dialect_specific.sql#44 drop +mysql/05_dialect_specific.sql#45 parse +mysql/05_dialect_specific.sql#46 drop +mysql/05_dialect_specific.sql#47 parse +mysql/05_dialect_specific.sql#48 parse +mysql/05_dialect_specific.sql#49 parse +mysql/05_dialect_specific.sql#50 parse +mysql/05_dialect_specific.sql#51 parse +mysql/05_dialect_specific.sql#52 parse +mysql/05_dialect_specific.sql#53 parse +mysql/05_dialect_specific.sql#54 parse +mysql/05_dialect_specific.sql#55 parse +mysql/05_dialect_specific.sql#56 parse +mysql/05_dialect_specific.sql#57 parse +mysql/05_dialect_specific.sql#58 parse +mysql/05_dialect_specific.sql#59 parse +mysql/05_dialect_specific.sql#60 parse +mysql/05_dialect_specific.sql#61 parse +mysql/05_dialect_specific.sql#62 parse +mysql/05_dialect_specific.sql#63 parse +mysql/05_dialect_specific.sql#64 parse +mysql/05_dialect_specific.sql#65 parse +mysql/05_dialect_specific.sql#67 parse +mysql/05_dialect_specific.sql#68 parse +mysql/05_dialect_specific.sql#69 parse +mysql/05_dialect_specific.sql#8 parse +mysql/05_dialect_specific.sql#9 reparse +mysql/06_wordpress.sql#12 parse +mysql/06_wordpress.sql#13 parse +mysql/06_wordpress.sql#19 parse +mysql/06_wordpress.sql#20 parse +mysql/06_wordpress.sql#3 parse +mysql/07_laravel_magento.sql#10 parse +mysql/07_laravel_magento.sql#11 parse +mysql/07_laravel_magento.sql#15 parse +mysql/07_laravel_magento.sql#7 parse +mysql/07_laravel_magento.sql#8 parse +mysql/08_etl_admin.sql#10 parse +mysql/08_etl_admin.sql#11 parse +mysql/08_etl_admin.sql#12 parse +mysql/08_etl_admin.sql#19 parse +mysql/08_etl_admin.sql#2 parse +mysql/08_etl_admin.sql#20 parse +mysql/08_etl_admin.sql#21 parse +mysql/08_etl_admin.sql#22 parse +mysql/08_etl_admin.sql#23 parse +mysql/08_etl_admin.sql#24 parse +mysql/08_etl_admin.sql#25 parse +mysql/08_etl_admin.sql#26 parse +mysql/08_etl_admin.sql#27 parse +mysql/08_etl_admin.sql#28 parse +mysql/08_etl_admin.sql#29 parse +mysql/08_etl_admin.sql#3 parse +mysql/08_etl_admin.sql#30 parse +mysql/08_etl_admin.sql#31 parse +mysql/08_etl_admin.sql#34 parse +mysql/08_etl_admin.sql#35 parse +mysql/08_etl_admin.sql#36 parse +mysql/08_etl_admin.sql#4 parse +mysql/08_etl_admin.sql#6 parse +mysql/08_etl_admin.sql#7 parse +mysql/08_etl_admin.sql#8 parse +mysql/08_etl_admin.sql#9 parse +mysql/09_deep_nesting.sql#1 parse +mysql/09_deep_nesting.sql#11 parse +mysql/09_deep_nesting.sql#22 drop +mysql/09_deep_nesting.sql#25 drop +mysql/09_deep_nesting.sql#26 parse +mysql/09_deep_nesting.sql#27 parse +mysql/09_deep_nesting.sql#30 parse +mysql/09_deep_nesting.sql#31 parse +mysql/09_deep_nesting.sql#5 parse +mysql/10_misc_functions.sql#1 parse +mysql/10_misc_functions.sql#17 reparse +mysql/10_misc_functions.sql#18 drop +mysql/10_misc_functions.sql#19 parse +mysql/10_misc_functions.sql#2 parse +mysql/10_misc_functions.sql#21 parse +mysql/10_misc_functions.sql#22 parse +mysql/10_misc_functions.sql#23 parse +mysql/10_misc_functions.sql#25 parse +mysql/10_misc_functions.sql#27 parse +mysql/10_misc_functions.sql#3 parse +mysql/10_misc_functions.sql#8 drop +mysql/11_stored_programs.sql#10 parse +mysql/11_stored_programs.sql#11 parse +mysql/11_stored_programs.sql#12 parse +mysql/11_stored_programs.sql#13 parse +mysql/11_stored_programs.sql#14 parse +mysql/11_stored_programs.sql#15 parse +mysql/11_stored_programs.sql#16 parse +mysql/11_stored_programs.sql#17 parse +mysql/11_stored_programs.sql#18 parse +mysql/11_stored_programs.sql#20 parse +mysql/11_stored_programs.sql#21 parse +mysql/11_stored_programs.sql#23 parse +mysql/11_stored_programs.sql#24 parse +mysql/11_stored_programs.sql#25 parse +mysql/11_stored_programs.sql#26 parse +mysql/11_stored_programs.sql#27 parse +mysql/11_stored_programs.sql#28 parse +mysql/11_stored_programs.sql#29 parse +mysql/11_stored_programs.sql#3 parse +mysql/11_stored_programs.sql#30 parse +mysql/11_stored_programs.sql#31 parse +mysql/11_stored_programs.sql#32 parse +mysql/11_stored_programs.sql#33 parse +mysql/11_stored_programs.sql#34 parse +mysql/11_stored_programs.sql#35 parse +mysql/11_stored_programs.sql#5 parse +mysql/11_stored_programs.sql#6 parse +mysql/11_stored_programs.sql#7 parse +mysql/11_stored_programs.sql#8 parse +mysql/12_orm_misc.sql#12 parse +mysql/12_orm_misc.sql#16 parse +mysql/12_orm_misc.sql#17 parse +mysql/12_orm_misc.sql#19 parse +mysql/12_orm_misc.sql#20 parse +mysql/12_orm_misc.sql#22 parse +mysql/12_orm_misc.sql#23 parse +mysql/12_orm_misc.sql#24 parse +mysql/12_orm_misc.sql#4 parse +mysql/12_orm_misc.sql#6 parse +oracle/01_app_crud.sql#10 parse +oracle/01_app_crud.sql#24 parse +oracle/01_app_crud.sql#25 parse +oracle/01_app_crud.sql#30 parse +oracle/01_app_crud.sql#31 idempotent +oracle/01_app_crud.sql#33 parse +oracle/01_app_crud.sql#34 parse +oracle/02_reporting.sql#17 drop +oracle/02_reporting.sql#20 parse +oracle/02_reporting.sql#21 parse +oracle/02_reporting.sql#22 parse +oracle/02_reporting.sql#26 parse +oracle/02_reporting.sql#28 parse +oracle/02_reporting.sql#3 parse +oracle/02_reporting.sql#36 parse +oracle/03_ddl_migrations.sql#10 parse +oracle/03_ddl_migrations.sql#11 reparse +oracle/03_ddl_migrations.sql#12 reparse +oracle/03_ddl_migrations.sql#13 parse +oracle/03_ddl_migrations.sql#17 parse +oracle/03_ddl_migrations.sql#18 parse +oracle/03_ddl_migrations.sql#19 parse +oracle/03_ddl_migrations.sql#20 parse +oracle/03_ddl_migrations.sql#21 parse +oracle/03_ddl_migrations.sql#22 parse +oracle/03_ddl_migrations.sql#24 parse +oracle/03_ddl_migrations.sql#25 parse +oracle/03_ddl_migrations.sql#26 parse +oracle/03_ddl_migrations.sql#27 parse +oracle/03_ddl_migrations.sql#28 parse +oracle/03_ddl_migrations.sql#29 parse +oracle/03_ddl_migrations.sql#3 parse +oracle/03_ddl_migrations.sql#30 parse +oracle/03_ddl_migrations.sql#31 parse +oracle/03_ddl_migrations.sql#37 parse +oracle/03_ddl_migrations.sql#38 parse +oracle/03_ddl_migrations.sql#39 parse +oracle/03_ddl_migrations.sql#4 parse +oracle/03_ddl_migrations.sql#40 parse +oracle/03_ddl_migrations.sql#41 parse +oracle/03_ddl_migrations.sql#42 parse +oracle/03_ddl_migrations.sql#43 parse +oracle/03_ddl_migrations.sql#44 parse +oracle/03_ddl_migrations.sql#45 parse +oracle/03_ddl_migrations.sql#47 parse +oracle/03_ddl_migrations.sql#5 parse +oracle/03_ddl_migrations.sql#50 parse +oracle/03_ddl_migrations.sql#53 parse +oracle/03_ddl_migrations.sql#54 parse +oracle/03_ddl_migrations.sql#55 parse +oracle/03_ddl_migrations.sql#56 parse +oracle/03_ddl_migrations.sql#57 parse +oracle/03_ddl_migrations.sql#58 parse +oracle/03_ddl_migrations.sql#59 parse +oracle/03_ddl_migrations.sql#6 parse +oracle/03_ddl_migrations.sql#60 parse +oracle/03_ddl_migrations.sql#7 drop +oracle/03_ddl_migrations.sql#8 parse +oracle/03_ddl_migrations.sql#9 parse +oracle/04_window_analytics.sql#10 parse +oracle/04_window_analytics.sql#11 parse +oracle/04_window_analytics.sql#24 parse +oracle/04_window_analytics.sql#8 parse +oracle/04_window_analytics.sql#9 parse +oracle/05_dialect_specific.sql#0 drop +oracle/05_dialect_specific.sql#1 parse +oracle/05_dialect_specific.sql#16 parse +oracle/05_dialect_specific.sql#17 parse +oracle/05_dialect_specific.sql#18 parse +oracle/05_dialect_specific.sql#19 parse +oracle/05_dialect_specific.sql#2 drop +oracle/05_dialect_specific.sql#20 parse +oracle/05_dialect_specific.sql#21 parse +oracle/05_dialect_specific.sql#29 parse +oracle/05_dialect_specific.sql#3 parse +oracle/05_dialect_specific.sql#32 parse +oracle/05_dialect_specific.sql#4 parse +oracle/05_dialect_specific.sql#5 drop +oracle/05_dialect_specific.sql#6 drop +oracle/05_dialect_specific.sql#7 drop +oracle/05_dialect_specific.sql#8 drop +oracle/05_dialect_specific.sql#9 parse +oracle/06_etl_merge.sql#1 parse +oracle/06_etl_merge.sql#10 parse +oracle/06_etl_merge.sql#12 parse +oracle/06_etl_merge.sql#15 parse +oracle/06_etl_merge.sql#16 parse +oracle/06_etl_merge.sql#17 parse +oracle/06_etl_merge.sql#19 parse +oracle/06_etl_merge.sql#2 parse +oracle/06_etl_merge.sql#20 parse +oracle/06_etl_merge.sql#21 parse +oracle/06_etl_merge.sql#22 parse +oracle/06_etl_merge.sql#23 parse +oracle/06_etl_merge.sql#24 reparse +oracle/06_etl_merge.sql#25 parse +oracle/06_etl_merge.sql#3 parse +oracle/06_etl_merge.sql#31 parse +oracle/06_etl_merge.sql#32 parse +oracle/06_etl_merge.sql#33 parse +oracle/06_etl_merge.sql#36 parse +oracle/06_etl_merge.sql#4 parse +oracle/06_etl_merge.sql#6 parse +oracle/06_etl_merge.sql#7 parse +oracle/06_etl_merge.sql#8 parse +oracle/07_admin_ops.sql#0 parse +oracle/07_admin_ops.sql#1 parse +oracle/07_admin_ops.sql#10 parse +oracle/07_admin_ops.sql#11 parse +oracle/07_admin_ops.sql#12 parse +oracle/07_admin_ops.sql#13 parse +oracle/07_admin_ops.sql#14 parse +oracle/07_admin_ops.sql#15 parse +oracle/07_admin_ops.sql#16 parse +oracle/07_admin_ops.sql#17 parse +oracle/07_admin_ops.sql#18 parse +oracle/07_admin_ops.sql#19 parse +oracle/07_admin_ops.sql#2 parse +oracle/07_admin_ops.sql#20 parse +oracle/07_admin_ops.sql#21 parse +oracle/07_admin_ops.sql#22 parse +oracle/07_admin_ops.sql#23 parse +oracle/07_admin_ops.sql#24 parse +oracle/07_admin_ops.sql#25 parse +oracle/07_admin_ops.sql#26 parse +oracle/07_admin_ops.sql#27 parse +oracle/07_admin_ops.sql#28 parse +oracle/07_admin_ops.sql#29 parse +oracle/07_admin_ops.sql#3 parse +oracle/07_admin_ops.sql#30 parse +oracle/07_admin_ops.sql#31 parse +oracle/07_admin_ops.sql#32 parse +oracle/07_admin_ops.sql#33 parse +oracle/07_admin_ops.sql#34 parse +oracle/07_admin_ops.sql#35 parse +oracle/07_admin_ops.sql#36 parse +oracle/07_admin_ops.sql#37 parse +oracle/07_admin_ops.sql#4 parse +oracle/07_admin_ops.sql#40 parse +oracle/07_admin_ops.sql#44 parse +oracle/07_admin_ops.sql#45 parse +oracle/07_admin_ops.sql#5 parse +oracle/07_admin_ops.sql#50 parse +oracle/07_admin_ops.sql#6 parse +oracle/07_admin_ops.sql#7 parse +oracle/07_admin_ops.sql#8 parse +oracle/07_admin_ops.sql#9 parse +oracle/08_complex_nested.sql#0 parse +oracle/08_complex_nested.sql#15 parse +oracle/08_complex_nested.sql#17 parse +oracle/08_complex_nested.sql#19 parse +oracle/09_plsql_blocks.sql#0 parse +oracle/09_plsql_blocks.sql#1 parse +oracle/09_plsql_blocks.sql#10 parse +oracle/09_plsql_blocks.sql#11 parse +oracle/09_plsql_blocks.sql#12 parse +oracle/09_plsql_blocks.sql#13 parse +oracle/09_plsql_blocks.sql#14 parse +oracle/09_plsql_blocks.sql#15 parse +oracle/09_plsql_blocks.sql#16 parse +oracle/09_plsql_blocks.sql#17 parse +oracle/09_plsql_blocks.sql#18 parse +oracle/09_plsql_blocks.sql#19 parse +oracle/09_plsql_blocks.sql#2 parse +oracle/09_plsql_blocks.sql#20 parse +oracle/09_plsql_blocks.sql#21 parse +oracle/09_plsql_blocks.sql#22 parse +oracle/09_plsql_blocks.sql#23 parse +oracle/09_plsql_blocks.sql#24 parse +oracle/09_plsql_blocks.sql#25 parse +oracle/09_plsql_blocks.sql#27 parse +oracle/09_plsql_blocks.sql#28 parse +oracle/09_plsql_blocks.sql#29 parse +oracle/09_plsql_blocks.sql#3 parse +oracle/09_plsql_blocks.sql#30 parse +oracle/09_plsql_blocks.sql#31 parse +oracle/09_plsql_blocks.sql#32 parse +oracle/09_plsql_blocks.sql#33 parse +oracle/09_plsql_blocks.sql#34 parse +oracle/09_plsql_blocks.sql#35 parse +oracle/09_plsql_blocks.sql#36 parse +oracle/09_plsql_blocks.sql#37 parse +oracle/09_plsql_blocks.sql#38 parse +oracle/09_plsql_blocks.sql#39 parse +oracle/09_plsql_blocks.sql#4 parse +oracle/09_plsql_blocks.sql#40 parse +oracle/09_plsql_blocks.sql#41 parse +oracle/09_plsql_blocks.sql#42 parse +oracle/09_plsql_blocks.sql#43 parse +oracle/09_plsql_blocks.sql#44 parse +oracle/09_plsql_blocks.sql#45 parse +oracle/09_plsql_blocks.sql#46 parse +oracle/09_plsql_blocks.sql#47 parse +oracle/09_plsql_blocks.sql#48 parse +oracle/09_plsql_blocks.sql#49 parse +oracle/09_plsql_blocks.sql#5 parse +oracle/09_plsql_blocks.sql#50 parse +oracle/09_plsql_blocks.sql#51 parse +oracle/09_plsql_blocks.sql#52 parse +oracle/09_plsql_blocks.sql#53 parse +oracle/09_plsql_blocks.sql#54 parse +oracle/09_plsql_blocks.sql#55 parse +oracle/09_plsql_blocks.sql#56 parse +oracle/09_plsql_blocks.sql#57 parse +oracle/09_plsql_blocks.sql#58 parse +oracle/09_plsql_blocks.sql#59 parse +oracle/09_plsql_blocks.sql#6 parse +oracle/09_plsql_blocks.sql#61 parse +oracle/09_plsql_blocks.sql#7 parse +oracle/09_plsql_blocks.sql#8 parse +oracle/09_plsql_blocks.sql#9 parse +postgresql/01_app_crud.sql#1 parse +postgresql/01_app_crud.sql#11 parse +postgresql/01_app_crud.sql#13 parse +postgresql/01_app_crud.sql#20 parse +postgresql/01_app_crud.sql#8 parse +postgresql/02_reporting.sql#27 drop +postgresql/02_reporting.sql#4 parse +postgresql/02_reporting.sql#7 parse +postgresql/02_reporting.sql#8 parse +postgresql/03_ddl_migrations.sql#1 parse +postgresql/03_ddl_migrations.sql#10 parse +postgresql/03_ddl_migrations.sql#12 parse +postgresql/03_ddl_migrations.sql#14 parse +postgresql/03_ddl_migrations.sql#16 parse +postgresql/03_ddl_migrations.sql#17 parse +postgresql/03_ddl_migrations.sql#19 parse +postgresql/03_ddl_migrations.sql#21 parse +postgresql/03_ddl_migrations.sql#24 parse +postgresql/03_ddl_migrations.sql#25 drop +postgresql/03_ddl_migrations.sql#26 parse +postgresql/03_ddl_migrations.sql#28 parse +postgresql/03_ddl_migrations.sql#29 parse +postgresql/03_ddl_migrations.sql#3 parse +postgresql/03_ddl_migrations.sql#32 parse +postgresql/03_ddl_migrations.sql#34 parse +postgresql/03_ddl_migrations.sql#37 parse +postgresql/03_ddl_migrations.sql#38 parse +postgresql/03_ddl_migrations.sql#39 parse +postgresql/03_ddl_migrations.sql#4 parse +postgresql/03_ddl_migrations.sql#42 parse +postgresql/03_ddl_migrations.sql#43 parse +postgresql/03_ddl_migrations.sql#44 parse +postgresql/03_ddl_migrations.sql#45 parse +postgresql/03_ddl_migrations.sql#46 parse +postgresql/03_ddl_migrations.sql#47 parse +postgresql/03_ddl_migrations.sql#48 parse +postgresql/03_ddl_migrations.sql#49 parse +postgresql/03_ddl_migrations.sql#5 parse +postgresql/03_ddl_migrations.sql#50 parse +postgresql/03_ddl_migrations.sql#59 parse +postgresql/03_ddl_migrations.sql#6 parse +postgresql/03_ddl_migrations.sql#60 parse +postgresql/03_ddl_migrations.sql#61 parse +postgresql/03_ddl_migrations.sql#62 parse +postgresql/03_ddl_migrations.sql#63 parse +postgresql/03_ddl_migrations.sql#64 parse +postgresql/03_ddl_migrations.sql#65 parse +postgresql/03_ddl_migrations.sql#7 reparse +postgresql/03_ddl_migrations.sql#9 parse +postgresql/04_window_analytics.sql#21 parse +postgresql/05_dialect_specific.sql#12 parse +postgresql/05_dialect_specific.sql#14 parse +postgresql/05_dialect_specific.sql#15 parse +postgresql/05_dialect_specific.sql#17 parse +postgresql/05_dialect_specific.sql#27 parse +postgresql/05_dialect_specific.sql#28 parse +postgresql/05_dialect_specific.sql#29 parse +postgresql/05_dialect_specific.sql#30 parse +postgresql/05_dialect_specific.sql#31 parse +postgresql/05_dialect_specific.sql#32 reparse +postgresql/05_dialect_specific.sql#33 parse +postgresql/05_dialect_specific.sql#35 parse +postgresql/05_dialect_specific.sql#36 parse +postgresql/05_dialect_specific.sql#38 parse +postgresql/05_dialect_specific.sql#39 parse +postgresql/05_dialect_specific.sql#40 parse +postgresql/05_dialect_specific.sql#41 parse +postgresql/05_dialect_specific.sql#42 parse +postgresql/05_dialect_specific.sql#43 parse +postgresql/05_dialect_specific.sql#46 parse +postgresql/05_dialect_specific.sql#47 parse +postgresql/05_dialect_specific.sql#49 parse +postgresql/05_dialect_specific.sql#51 parse +postgresql/05_dialect_specific.sql#52 parse +postgresql/05_dialect_specific.sql#53 parse +postgresql/05_dialect_specific.sql#54 parse +postgresql/05_dialect_specific.sql#55 parse +postgresql/05_dialect_specific.sql#56 parse +postgresql/06_cte_dml_etl.sql#11 parse +postgresql/06_cte_dml_etl.sql#12 parse +postgresql/06_cte_dml_etl.sql#14 parse +postgresql/06_cte_dml_etl.sql#15 parse +postgresql/06_cte_dml_etl.sql#16 parse +postgresql/06_cte_dml_etl.sql#18 parse +postgresql/06_cte_dml_etl.sql#22 parse +postgresql/06_cte_dml_etl.sql#3 drop +postgresql/06_cte_dml_etl.sql#5 parse +postgresql/06_cte_dml_etl.sql#6 parse +postgresql/06_cte_dml_etl.sql#7 parse +postgresql/06_cte_dml_etl.sql#9 parse +postgresql/07_admin_ops.sql#0 parse +postgresql/07_admin_ops.sql#1 parse +postgresql/07_admin_ops.sql#10 parse +postgresql/07_admin_ops.sql#11 parse +postgresql/07_admin_ops.sql#12 parse +postgresql/07_admin_ops.sql#14 drop +postgresql/07_admin_ops.sql#15 parse +postgresql/07_admin_ops.sql#16 drop +postgresql/07_admin_ops.sql#17 parse +postgresql/07_admin_ops.sql#18 parse +postgresql/07_admin_ops.sql#19 parse +postgresql/07_admin_ops.sql#2 parse +postgresql/07_admin_ops.sql#20 parse +postgresql/07_admin_ops.sql#3 parse +postgresql/07_admin_ops.sql#33 parse +postgresql/07_admin_ops.sql#34 parse +postgresql/07_admin_ops.sql#35 parse +postgresql/07_admin_ops.sql#36 parse +postgresql/07_admin_ops.sql#37 parse +postgresql/07_admin_ops.sql#38 parse +postgresql/07_admin_ops.sql#39 parse +postgresql/07_admin_ops.sql#4 parse +postgresql/07_admin_ops.sql#40 parse +postgresql/07_admin_ops.sql#41 parse +postgresql/07_admin_ops.sql#42 parse +postgresql/07_admin_ops.sql#43 parse +postgresql/07_admin_ops.sql#44 parse +postgresql/07_admin_ops.sql#5 parse +postgresql/07_admin_ops.sql#6 parse +postgresql/07_admin_ops.sql#7 parse +postgresql/07_admin_ops.sql#8 parse +postgresql/07_admin_ops.sql#9 parse +postgresql/08_supabase_rls_misc.sql#0 parse +postgresql/08_supabase_rls_misc.sql#10 parse +postgresql/08_supabase_rls_misc.sql#16 parse +postgresql/08_supabase_rls_misc.sql#17 parse +postgresql/08_supabase_rls_misc.sql#26 parse +postgresql/08_supabase_rls_misc.sql#28 parse +postgresql/08_supabase_rls_misc.sql#3 parse +postgresql/08_supabase_rls_misc.sql#4 parse +postgresql/08_supabase_rls_misc.sql#5 parse +postgresql/08_supabase_rls_misc.sql#6 parse +snowflake/01_app_crud.sql#10 parse +snowflake/01_app_crud.sql#18 parse +snowflake/01_app_crud.sql#19 parse +snowflake/02_reporting.sql#21 drop +snowflake/02_reporting.sql#4 parse +snowflake/03_ddl_migrations.sql#1 parse +snowflake/03_ddl_migrations.sql#10 parse +snowflake/03_ddl_migrations.sql#12 parse +snowflake/03_ddl_migrations.sql#16 parse +snowflake/03_ddl_migrations.sql#2 drop +snowflake/03_ddl_migrations.sql#20 parse +snowflake/03_ddl_migrations.sql#24 parse +snowflake/03_ddl_migrations.sql#25 parse +snowflake/03_ddl_migrations.sql#26 parse +snowflake/03_ddl_migrations.sql#3 parse +snowflake/03_ddl_migrations.sql#30 parse +snowflake/03_ddl_migrations.sql#31 reparse +snowflake/03_ddl_migrations.sql#32 reparse +snowflake/03_ddl_migrations.sql#33 reparse +snowflake/03_ddl_migrations.sql#34 reparse +snowflake/03_ddl_migrations.sql#35 reparse +snowflake/03_ddl_migrations.sql#36 parse +snowflake/03_ddl_migrations.sql#5 parse +snowflake/03_ddl_migrations.sql#6 parse +snowflake/03_ddl_migrations.sql#7 parse +snowflake/03_ddl_migrations.sql#8 reparse +snowflake/03_ddl_migrations.sql#9 reparse +snowflake/04_window_analytics.sql#1 drop +snowflake/04_window_analytics.sql#10 parse +snowflake/04_window_analytics.sql#14 drop +snowflake/04_window_analytics.sql#2 drop +snowflake/04_window_analytics.sql#3 drop +snowflake/04_window_analytics.sql#4 drop +snowflake/05_dialect_specific.sql#14 idempotent +snowflake/05_dialect_specific.sql#15 idempotent +snowflake/05_dialect_specific.sql#16 drop +snowflake/05_dialect_specific.sql#17 drop +snowflake/05_dialect_specific.sql#18 drop +snowflake/05_dialect_specific.sql#19 drop +snowflake/05_dialect_specific.sql#20 drop +snowflake/05_dialect_specific.sql#21 drop +snowflake/05_dialect_specific.sql#22 parse +snowflake/05_dialect_specific.sql#23 drop +snowflake/05_dialect_specific.sql#24 parse +snowflake/05_dialect_specific.sql#25 parse +snowflake/05_dialect_specific.sql#27 parse +snowflake/05_dialect_specific.sql#28 parse +snowflake/05_dialect_specific.sql#40 parse +snowflake/05_dialect_specific.sql#42 idempotent +snowflake/05_dialect_specific.sql#43 parse +snowflake/05_dialect_specific.sql#45 parse +snowflake/05_dialect_specific.sql#46 parse +snowflake/05_dialect_specific.sql#47 parse +snowflake/05_dialect_specific.sql#48 parse +snowflake/05_dialect_specific.sql#49 parse +snowflake/05_dialect_specific.sql#5 parse +snowflake/05_dialect_specific.sql#50 parse +snowflake/05_dialect_specific.sql#8 parse +snowflake/06_etl_merge.sql#10 parse +snowflake/06_etl_merge.sql#13 reparse +snowflake/06_etl_merge.sql#14 reparse +snowflake/06_etl_merge.sql#15 parse +snowflake/06_etl_merge.sql#16 reparse +snowflake/06_etl_merge.sql#17 reparse +snowflake/06_etl_merge.sql#18 parse +snowflake/06_etl_merge.sql#19 parse +snowflake/06_etl_merge.sql#2 parse +snowflake/06_etl_merge.sql#20 reparse +snowflake/06_etl_merge.sql#21 parse +snowflake/06_etl_merge.sql#22 reparse +snowflake/06_etl_merge.sql#23 parse +snowflake/06_etl_merge.sql#25 reparse +snowflake/06_etl_merge.sql#26 reparse +snowflake/06_etl_merge.sql#27 reparse +snowflake/06_etl_merge.sql#28 reparse +snowflake/06_etl_merge.sql#29 parse +snowflake/06_etl_merge.sql#30 parse +snowflake/06_etl_merge.sql#31 parse +snowflake/06_etl_merge.sql#32 parse +snowflake/06_etl_merge.sql#34 parse +snowflake/06_etl_merge.sql#35 parse +snowflake/06_etl_merge.sql#4 reparse +snowflake/06_etl_merge.sql#5 reparse +snowflake/06_etl_merge.sql#6 reparse +snowflake/06_etl_merge.sql#7 reparse +snowflake/07_admin.sql#0 reparse +snowflake/07_admin.sql#1 reparse +snowflake/07_admin.sql#10 parse +snowflake/07_admin.sql#11 parse +snowflake/07_admin.sql#14 parse +snowflake/07_admin.sql#18 drop +snowflake/07_admin.sql#19 drop +snowflake/07_admin.sql#2 reparse +snowflake/07_admin.sql#20 drop +snowflake/07_admin.sql#21 reparse +snowflake/07_admin.sql#22 parse +snowflake/07_admin.sql#23 parse +snowflake/07_admin.sql#24 reparse +snowflake/07_admin.sql#25 parse +snowflake/07_admin.sql#26 parse +snowflake/07_admin.sql#27 parse +snowflake/07_admin.sql#28 parse +snowflake/07_admin.sql#29 parse +snowflake/07_admin.sql#3 reparse +snowflake/07_admin.sql#30 parse +snowflake/07_admin.sql#31 parse +snowflake/07_admin.sql#32 parse +snowflake/07_admin.sql#33 parse +snowflake/07_admin.sql#34 parse +snowflake/07_admin.sql#35 parse +snowflake/07_admin.sql#36 parse +snowflake/07_admin.sql#37 parse +snowflake/07_admin.sql#4 reparse +snowflake/07_admin.sql#43 parse +snowflake/07_admin.sql#44 parse +snowflake/07_admin.sql#46 parse +snowflake/07_admin.sql#47 parse +snowflake/07_admin.sql#48 parse +snowflake/07_admin.sql#49 parse +snowflake/07_admin.sql#50 parse +snowflake/07_admin.sql#51 parse +snowflake/07_admin.sql#52 drop +snowflake/07_admin.sql#6 parse +snowflake/07_admin.sql#7 parse +snowflake/07_admin.sql#8 parse +snowflake/07_admin.sql#9 parse +snowflake/08_pivot_match.sql#1 parse +snowflake/08_pivot_match.sql#13 parse +snowflake/08_pivot_match.sql#14 parse +snowflake/08_pivot_match.sql#2 parse +snowflake/08_pivot_match.sql#6 drop +snowflake/08_pivot_match.sql#7 drop +snowflake/09_dbt_models.sql#0 parse +snowflake/09_dbt_models.sql#1 parse +snowflake/09_dbt_models.sql#12 parse +snowflake/09_dbt_models.sql#13 drop +snowflake/09_dbt_models.sql#15 parse +snowflake/09_dbt_models.sql#17 parse +snowflake/09_dbt_models.sql#2 parse +snowflake/10_complex_nested.sql#12 parse +snowflake/10_complex_nested.sql#19 drop +snowflake/10_complex_nested.sql#21 drop +snowflake/10_complex_nested.sql#22 parse +snowflake/10_complex_nested.sql#26 parse +snowflake/10_complex_nested.sql#34 parse +snowflake/10_complex_nested.sql#39 parse +sqlite/01_app_crud.sql#0 drop +sqlite/01_app_crud.sql#4 parse +sqlite/01_app_crud.sql#7 parse +sqlite/01_app_crud.sql#8 parse +sqlite/02_upsert_replace.sql#0 drop +sqlite/02_upsert_replace.sql#13 parse +sqlite/02_upsert_replace.sql#16 parse +sqlite/02_upsert_replace.sql#17 parse +sqlite/02_upsert_replace.sql#19 parse +sqlite/02_upsert_replace.sql#2 parse +sqlite/02_upsert_replace.sql#3 parse +sqlite/02_upsert_replace.sql#4 parse +sqlite/02_upsert_replace.sql#5 parse +sqlite/02_upsert_replace.sql#6 parse +sqlite/02_upsert_replace.sql#7 parse +sqlite/02_upsert_replace.sql#8 parse +sqlite/03_ddl_migrations.sql#1 drop +sqlite/03_ddl_migrations.sql#11 parse +sqlite/03_ddl_migrations.sql#13 parse +sqlite/03_ddl_migrations.sql#15 drop +sqlite/03_ddl_migrations.sql#23 parse +sqlite/03_ddl_migrations.sql#24 parse +sqlite/03_ddl_migrations.sql#25 parse +sqlite/03_ddl_migrations.sql#26 parse +sqlite/03_ddl_migrations.sql#27 parse +sqlite/03_ddl_migrations.sql#28 parse +sqlite/03_ddl_migrations.sql#29 parse +sqlite/03_ddl_migrations.sql#3 parse +sqlite/03_ddl_migrations.sql#30 parse +sqlite/03_ddl_migrations.sql#31 parse +sqlite/03_ddl_migrations.sql#32 parse +sqlite/03_ddl_migrations.sql#33 parse +sqlite/03_ddl_migrations.sql#34 parse +sqlite/03_ddl_migrations.sql#35 parse +sqlite/03_ddl_migrations.sql#36 parse +sqlite/03_ddl_migrations.sql#37 parse +sqlite/03_ddl_migrations.sql#38 parse +sqlite/03_ddl_migrations.sql#4 parse +sqlite/03_ddl_migrations.sql#5 parse +sqlite/03_ddl_migrations.sql#6 parse +sqlite/03_ddl_migrations.sql#7 drop +sqlite/03_ddl_migrations.sql#9 reparse +sqlite/05_dialect_specific.sql#0 parse +sqlite/05_dialect_specific.sql#1 parse +sqlite/05_dialect_specific.sql#10 parse +sqlite/05_dialect_specific.sql#11 parse +sqlite/05_dialect_specific.sql#12 parse +sqlite/05_dialect_specific.sql#14 parse +sqlite/05_dialect_specific.sql#15 parse +sqlite/05_dialect_specific.sql#16 parse +sqlite/05_dialect_specific.sql#17 parse +sqlite/05_dialect_specific.sql#18 parse +sqlite/05_dialect_specific.sql#20 parse +sqlite/05_dialect_specific.sql#23 parse +sqlite/05_dialect_specific.sql#26 parse +sqlite/05_dialect_specific.sql#27 parse +sqlite/05_dialect_specific.sql#30 parse +sqlite/05_dialect_specific.sql#31 parse +sqlite/05_dialect_specific.sql#42 parse +sqlite/05_dialect_specific.sql#43 parse +sqlite/05_dialect_specific.sql#44 parse +sqlite/05_dialect_specific.sql#59 parse +sqlite/05_dialect_specific.sql#61 parse +sqlite/05_dialect_specific.sql#65 parse +sqlite/05_dialect_specific.sql#9 parse +sqlite/06_json.sql#16 parse +sqlite/06_json.sql#17 parse +sqlite/06_json.sql#18 parse +sqlite/06_json.sql#21 parse +sqlite/06_json.sql#22 parse +sqlite/07_ctes_recursive.sql#10 parse +sqlite/07_ctes_recursive.sql#11 parse +sqlite/07_ctes_recursive.sql#2 parse +sqlite/07_ctes_recursive.sql#7 drop +sqlite/07_ctes_recursive.sql#8 drop +sqlite/08_reporting.sql#13 parse +sqlite/08_reporting.sql#15 parse +sqlite/08_reporting.sql#16 parse +sqlite/09_etl_admin.sql#1 drop +sqlite/09_etl_admin.sql#10 parse +sqlite/09_etl_admin.sql#17 parse +sqlite/09_etl_admin.sql#18 parse +sqlite/09_etl_admin.sql#19 parse +sqlite/09_etl_admin.sql#2 parse +sqlite/09_etl_admin.sql#28 parse +sqlite/09_etl_admin.sql#29 parse +sqlite/09_etl_admin.sql#30 parse +sqlite/09_etl_admin.sql#31 drop +sqlite/09_etl_admin.sql#32 parse +sqlite/09_etl_admin.sql#33 parse +sqlite/09_etl_admin.sql#34 parse +sqlite/09_etl_admin.sql#35 parse +sqlite/09_etl_admin.sql#36 parse +sqlite/09_etl_admin.sql#37 parse +sqlite/09_etl_admin.sql#38 parse +sqlite/09_etl_admin.sql#39 parse +sqlite/09_etl_admin.sql#40 parse +sqlite/09_etl_admin.sql#8 parse +sqlite/10_mobile_offline_sync.sql#0 drop +sqlite/10_mobile_offline_sync.sql#15 parse +sqlite/10_mobile_offline_sync.sql#16 parse +sqlite/10_mobile_offline_sync.sql#18 parse +sqlite/10_mobile_offline_sync.sql#20 drop +sqlite/10_mobile_offline_sync.sql#21 parse +sqlite/10_mobile_offline_sync.sql#4 parse +sqlite/10_mobile_offline_sync.sql#5 parse +sqlite/10_mobile_offline_sync.sql#6 parse +sqlite/10_mobile_offline_sync.sql#7 parse +sqlite/10_mobile_offline_sync.sql#8 parse +sqlite/11_deep_nesting.sql#14 parse +sqlite/11_deep_nesting.sql#19 parse +sqlite/11_deep_nesting.sql#30 parse +sqlite/11_deep_nesting.sql#31 parse +sqlite/11_deep_nesting.sql#7 parse +sqlite/12_orm_generated.sql#10 parse +sqlite/12_orm_generated.sql#11 drop +sqlite/12_orm_generated.sql#12 drop +sqlite/12_orm_generated.sql#14 parse +sqlite/12_orm_generated.sql#16 parse +sqlite/12_orm_generated.sql#8 parse +sqlite/12_orm_generated.sql#9 drop +sqlserver/01_app_crud.sql#1 parse +sqlserver/01_app_crud.sql#10 parse +sqlserver/01_app_crud.sql#11 parse +sqlserver/01_app_crud.sql#12 parse +sqlserver/01_app_crud.sql#13 parse +sqlserver/01_app_crud.sql#14 parse +sqlserver/01_app_crud.sql#16 parse +sqlserver/01_app_crud.sql#18 parse +sqlserver/01_app_crud.sql#2 parse +sqlserver/01_app_crud.sql#20 parse +sqlserver/01_app_crud.sql#3 parse +sqlserver/01_app_crud.sql#4 parse +sqlserver/01_app_crud.sql#5 parse +sqlserver/01_app_crud.sql#6 parse +sqlserver/01_app_crud.sql#7 parse +sqlserver/01_app_crud.sql#8 parse +sqlserver/01_app_crud.sql#9 parse +sqlserver/02_reporting.sql#10 parse +sqlserver/02_reporting.sql#17 parse +sqlserver/02_reporting.sql#18 parse +sqlserver/02_reporting.sql#19 parse +sqlserver/02_reporting.sql#20 parse +sqlserver/02_reporting.sql#21 drop +sqlserver/02_reporting.sql#23 parse +sqlserver/02_reporting.sql#24 parse +sqlserver/02_reporting.sql#7 parse +sqlserver/02_reporting.sql#8 drop +sqlserver/02_reporting.sql#9 parse +sqlserver/03_ddl_migrations.sql#0 parse +sqlserver/03_ddl_migrations.sql#1 parse +sqlserver/03_ddl_migrations.sql#10 parse +sqlserver/03_ddl_migrations.sql#11 parse +sqlserver/03_ddl_migrations.sql#12 parse +sqlserver/03_ddl_migrations.sql#13 parse +sqlserver/03_ddl_migrations.sql#15 parse +sqlserver/03_ddl_migrations.sql#16 parse +sqlserver/03_ddl_migrations.sql#18 parse +sqlserver/03_ddl_migrations.sql#2 parse +sqlserver/03_ddl_migrations.sql#23 parse +sqlserver/03_ddl_migrations.sql#25 parse +sqlserver/03_ddl_migrations.sql#26 parse +sqlserver/03_ddl_migrations.sql#27 parse +sqlserver/03_ddl_migrations.sql#29 parse +sqlserver/03_ddl_migrations.sql#30 parse +sqlserver/03_ddl_migrations.sql#31 parse +sqlserver/03_ddl_migrations.sql#4 parse +sqlserver/03_ddl_migrations.sql#5 parse +sqlserver/03_ddl_migrations.sql#6 parse +sqlserver/03_ddl_migrations.sql#7 parse +sqlserver/03_ddl_migrations.sql#8 parse +sqlserver/03_ddl_migrations.sql#9 parse +sqlserver/05_dialect_specific.sql#0 reparse +sqlserver/05_dialect_specific.sql#1 reparse +sqlserver/05_dialect_specific.sql#11 parse +sqlserver/05_dialect_specific.sql#12 parse +sqlserver/05_dialect_specific.sql#13 parse +sqlserver/05_dialect_specific.sql#14 parse +sqlserver/05_dialect_specific.sql#16 parse +sqlserver/05_dialect_specific.sql#17 parse +sqlserver/05_dialect_specific.sql#18 parse +sqlserver/05_dialect_specific.sql#19 parse +sqlserver/05_dialect_specific.sql#2 parse +sqlserver/05_dialect_specific.sql#26 parse +sqlserver/05_dialect_specific.sql#27 parse +sqlserver/05_dialect_specific.sql#28 parse +sqlserver/05_dialect_specific.sql#3 parse +sqlserver/05_dialect_specific.sql#33 parse +sqlserver/05_dialect_specific.sql#4 parse +sqlserver/05_dialect_specific.sql#5 parse +sqlserver/05_dialect_specific.sql#6 parse +sqlserver/05_dialect_specific.sql#7 parse +sqlserver/05_dialect_specific.sql#8 parse +sqlserver/05_dialect_specific.sql#9 parse +sqlserver/06_merge_etl.sql#0 parse +sqlserver/06_merge_etl.sql#1 parse +sqlserver/06_merge_etl.sql#10 parse +sqlserver/06_merge_etl.sql#11 parse +sqlserver/06_merge_etl.sql#12 parse +sqlserver/06_merge_etl.sql#13 parse +sqlserver/06_merge_etl.sql#14 parse +sqlserver/06_merge_etl.sql#16 parse +sqlserver/06_merge_etl.sql#18 parse +sqlserver/06_merge_etl.sql#2 parse +sqlserver/06_merge_etl.sql#3 drop +sqlserver/06_merge_etl.sql#4 parse +sqlserver/07_ctes_deep.sql#0 parse +sqlserver/07_ctes_deep.sql#1 parse +sqlserver/07_ctes_deep.sql#11 parse +sqlserver/07_ctes_deep.sql#14 parse +sqlserver/07_ctes_deep.sql#15 parse +sqlserver/07_ctes_deep.sql#16 parse +sqlserver/07_ctes_deep.sql#2 parse +sqlserver/08_procs_batches.sql#0 parse +sqlserver/08_procs_batches.sql#1 parse +sqlserver/08_procs_batches.sql#10 parse +sqlserver/08_procs_batches.sql#11 parse +sqlserver/08_procs_batches.sql#12 parse +sqlserver/08_procs_batches.sql#13 parse +sqlserver/08_procs_batches.sql#14 parse +sqlserver/08_procs_batches.sql#15 parse +sqlserver/08_procs_batches.sql#16 parse +sqlserver/08_procs_batches.sql#17 parse +sqlserver/08_procs_batches.sql#18 parse +sqlserver/08_procs_batches.sql#19 parse +sqlserver/08_procs_batches.sql#2 parse +sqlserver/08_procs_batches.sql#20 parse +sqlserver/08_procs_batches.sql#21 parse +sqlserver/08_procs_batches.sql#22 parse +sqlserver/08_procs_batches.sql#23 parse +sqlserver/08_procs_batches.sql#24 parse +sqlserver/08_procs_batches.sql#25 parse +sqlserver/08_procs_batches.sql#26 parse +sqlserver/08_procs_batches.sql#27 parse +sqlserver/08_procs_batches.sql#28 parse +sqlserver/08_procs_batches.sql#3 parse +sqlserver/08_procs_batches.sql#30 parse +sqlserver/08_procs_batches.sql#31 parse +sqlserver/08_procs_batches.sql#32 parse +sqlserver/08_procs_batches.sql#33 parse +sqlserver/08_procs_batches.sql#34 parse +sqlserver/08_procs_batches.sql#35 parse +sqlserver/08_procs_batches.sql#36 parse +sqlserver/08_procs_batches.sql#37 parse +sqlserver/08_procs_batches.sql#38 parse +sqlserver/08_procs_batches.sql#39 parse +sqlserver/08_procs_batches.sql#4 parse +sqlserver/08_procs_batches.sql#40 parse +sqlserver/08_procs_batches.sql#5 parse +sqlserver/08_procs_batches.sql#6 parse +sqlserver/08_procs_batches.sql#7 parse +sqlserver/08_procs_batches.sql#8 parse +sqlserver/08_procs_batches.sql#9 parse +sqlserver/09_admin_dmv.sql#1 parse +sqlserver/09_admin_dmv.sql#13 parse +sqlserver/09_admin_dmv.sql#15 parse +sqlserver/09_admin_dmv.sql#16 parse +sqlserver/09_admin_dmv.sql#17 parse +sqlserver/09_admin_dmv.sql#18 parse +sqlserver/09_admin_dmv.sql#19 parse +sqlserver/09_admin_dmv.sql#3 parse +sqlserver/09_admin_dmv.sql#4 parse +sqlserver/09_admin_dmv.sql#7 parse +sqlserver/09_admin_dmv.sql#9 parse +sqlserver/10_etl_mixed.sql#1 parse +sqlserver/10_etl_mixed.sql#10 parse +sqlserver/10_etl_mixed.sql#11 parse +sqlserver/10_etl_mixed.sql#12 parse +sqlserver/10_etl_mixed.sql#13 parse +sqlserver/10_etl_mixed.sql#14 parse +sqlserver/10_etl_mixed.sql#15 parse +sqlserver/10_etl_mixed.sql#18 drop +sqlserver/10_etl_mixed.sql#19 parse +sqlserver/10_etl_mixed.sql#2 parse +sqlserver/10_etl_mixed.sql#20 parse +sqlserver/10_etl_mixed.sql#21 parse +sqlserver/10_etl_mixed.sql#25 reparse +sqlserver/10_etl_mixed.sql#28 parse +sqlserver/10_etl_mixed.sql#8 parse +sqlserver/11_batches_no_semicolons.sql#0 parse diff --git a/testdata/realworld/mariadb/01_app_crud.sql b/testdata/realworld/mariadb/01_app_crud.sql new file mode 100644 index 00000000..e75c75a7 --- /dev/null +++ b/testdata/realworld/mariadb/01_app_crud.sql @@ -0,0 +1,36 @@ +-- ORM-generated CRUD for a web application (users, orders, products) +SELECT `id`, `email`, `created_at` FROM `users` WHERE `id` = 42 LIMIT 1; +SELECT u.id, u.email FROM users AS u WHERE u.email = 'a@example.com' AND u.deleted_at IS NULL; +SELECT * FROM products WHERE category_id IN (1, 2, 3) AND price BETWEEN 10 AND 100 ORDER BY price DESC LIMIT 20 OFFSET 40; +SELECT * FROM products WHERE name LIKE '%widget%' ESCAPE '\\' OR sku LIKE 'WD-%'; +INSERT INTO users (email, name, created_at) VALUES ('a@example.com', 'Alice', NOW()); +INSERT INTO users (email, name) VALUES ('b@example.com', 'Bob'), ('c@example.com', 'Carol'), ('d@example.com', 'Dave'); +INSERT INTO users SET email = 'e@example.com', name = 'Eve'; +INSERT IGNORE INTO tags (name) VALUES ('go'), ('sql'); +INSERT INTO page_views (page_id, views) VALUES (1, 1) ON DUPLICATE KEY UPDATE views = views + 1; +INSERT INTO users (email, name) SELECT email, name FROM staging_users WHERE imported = 0; +REPLACE INTO settings (k, v) VALUES ('theme', 'dark'); +UPDATE users SET name = 'Alice B', updated_at = NOW() WHERE id = 1; +UPDATE products SET stock = stock - 1 WHERE id = 7 AND stock > 0; +UPDATE orders o JOIN users u ON u.id = o.user_id SET o.status = 'archived' WHERE u.deleted_at IS NOT NULL; +UPDATE products SET price = price * 1.1 WHERE category_id = 3 ORDER BY id LIMIT 100; +DELETE FROM sessions WHERE expires_at < NOW(); +DELETE FROM carts WHERE user_id = 5 ORDER BY id LIMIT 10; +DELETE o FROM orders o LEFT JOIN users u ON u.id = o.user_id WHERE u.id IS NULL; +SELECT o.id, o.total, u.email FROM orders o INNER JOIN users u ON u.id = o.user_id WHERE o.status = 'paid'; +SELECT o.id, i.product_id, i.qty FROM orders o LEFT JOIN order_items i ON i.order_id = o.id WHERE o.user_id = 3; +SELECT COUNT(*) FROM orders WHERE created_at >= '2025-01-01'; +SELECT p.* FROM products p WHERE EXISTS (SELECT 1 FROM order_items i WHERE i.product_id = p.id); +SELECT * FROM users WHERE id NOT IN (SELECT user_id FROM bans); +SELECT id FROM users WHERE id = ? AND status = ?; +SELECT * FROM users WHERE (status, role) IN (('active', 'admin'), ('active', 'editor')); +SELECT * FROM orders WHERE user_id = 9 ORDER BY created_at DESC LIMIT 10 FOR UPDATE; +SELECT * FROM inventory WHERE sku = 'X1' LOCK IN SHARE MODE; +START TRANSACTION; +COMMIT; +SELECT LAST_INSERT_ID(); +SELECT DATABASE(), VERSION(), CURRENT_USER(); +SELECT IFNULL(nickname, name) AS display, COALESCE(phone, mobile, 'n/a') AS contact FROM users; +SELECT CASE WHEN total > 1000 THEN 'big' WHEN total > 100 THEN 'mid' ELSE 'small' END AS bucket FROM orders; +SELECT CONCAT(first_name, ' ', last_name) AS full_name, UPPER(email) FROM users; +SELECT DATE_FORMAT(created_at, '%Y-%m') AS ym, COUNT(*) FROM orders GROUP BY ym; diff --git a/testdata/realworld/mariadb/02_reporting.sql b/testdata/realworld/mariadb/02_reporting.sql new file mode 100644 index 00000000..2e295e70 --- /dev/null +++ b/testdata/realworld/mariadb/02_reporting.sql @@ -0,0 +1,46 @@ +-- Dashboard and reporting queries with aggregation, subqueries and CTEs +SELECT status, COUNT(*) AS n, SUM(total) AS revenue FROM orders GROUP BY status HAVING SUM(total) > 1000 ORDER BY revenue DESC; +SELECT YEAR(created_at) AS y, MONTH(created_at) AS m, SUM(total) FROM orders GROUP BY YEAR(created_at), MONTH(created_at) WITH ROLLUP; +SELECT category_id, COUNT(DISTINCT user_id) AS buyers FROM order_items i JOIN orders o ON o.id = i.order_id GROUP BY category_id; +SELECT GROUP_CONCAT(name ORDER BY name SEPARATOR ', ') FROM tags; +SELECT user_id, GROUP_CONCAT(DISTINCT status) FROM orders GROUP BY user_id; +SELECT d.dt, COALESCE(s.cnt, 0) FROM calendar d LEFT JOIN (SELECT DATE(created_at) AS dt, COUNT(*) AS cnt FROM orders GROUP BY DATE(created_at)) s ON s.dt = d.dt; +WITH monthly AS (SELECT DATE_FORMAT(created_at, '%Y-%m-01') AS month, SUM(total) AS rev FROM orders GROUP BY 1) SELECT month, rev FROM monthly ORDER BY month; +WITH a AS (SELECT user_id, SUM(total) AS t FROM orders GROUP BY user_id), b AS (SELECT user_id, COUNT(*) AS c FROM sessions GROUP BY user_id), c AS (SELECT a.user_id, a.t, b.c FROM a JOIN b USING (user_id)) SELECT * FROM c WHERE t > 500; +SELECT u.id, (SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) AS order_count FROM users u; +SELECT * FROM (SELECT user_id, MAX(total) AS mx FROM orders GROUP BY user_id) AS t WHERE mx > 100; +SELECT a.id FROM a JOIN b ON a.id = b.a_id JOIN c ON c.b_id = b.id JOIN d ON d.c_id = c.id LEFT JOIN e ON e.d_id = d.id LEFT JOIN f ON f.e_id = e.id WHERE f.id IS NULL; +SELECT t1.id FROM t1 NATURAL JOIN t2; +SELECT t1.id FROM t1 CROSS JOIN t2 WHERE t1.x = t2.y; +SELECT t1.id FROM t1 STRAIGHT_JOIN t2 ON t1.id = t2.id; +SELECT t1.id FROM t1 RIGHT JOIN t2 ON t1.id = t2.id; +SELECT * FROM t1 UNION SELECT * FROM t2 UNION ALL SELECT * FROM t3 ORDER BY 1 LIMIT 10; +(SELECT id FROM t1 ORDER BY id LIMIT 5) UNION ALL (SELECT id FROM t2 ORDER BY id LIMIT 5); +SELECT SQL_CALC_FOUND_ROWS id FROM users LIMIT 10; +SELECT FOUND_ROWS(); +SELECT * FROM t1 USE INDEX (idx_a) WHERE a = 1; +SELECT * FROM t1 FORCE INDEX (idx_a, idx_b) WHERE a = 1 OR b = 2; +SELECT * FROM t1 IGNORE INDEX FOR ORDER BY (idx_a) ORDER BY a; +SELECT AVG(total), STDDEV(total), MIN(total), MAX(total) FROM orders; +SELECT IF(total > 100, 'hi', 'lo') AS tier, COUNT(*) FROM orders GROUP BY tier; +SELECT DATEDIFF(NOW(), created_at) AS age_days, TIMESTAMPDIFF(HOUR, created_at, NOW()) FROM users; +SELECT DATE_ADD(NOW(), INTERVAL 7 DAY), DATE_SUB(CURDATE(), INTERVAL 1 MONTH); +SELECT * FROM orders WHERE created_at >= NOW() - INTERVAL 30 DAY; +SELECT id, total INTO @id, @total FROM orders WHERE id = 1; +SELECT CAST(total AS DECIMAL(10, 2)), CAST(id AS CHAR), CONVERT(name, CHAR(50)) FROM orders; +SELECT 1 + 2 * 3, (1 + 2) * 3, 10 DIV 3, 10 MOD 3, 10 % 3, 2 << 3, 5 & 3, 5 | 3, 5 ^ 3, ~5; +SELECT * FROM users WHERE name REGEXP '^A' AND email NOT REGEXP 'test'; +SELECT * FROM users WHERE a <=> NULL AND b IS NOT NULL AND NOT c; +SELECT 'a' || 'b'; +SELECT * FROM t WHERE x = 1 XOR y = 2; +SHOW TABLES; +SHOW COLUMNS FROM users; +SHOW CREATE TABLE users; +SHOW INDEX FROM orders; +SHOW VARIABLES LIKE 'max_connections'; +SHOW FULL PROCESSLIST; +SHOW GRANTS FOR 'app'@'localhost'; +SHOW STATUS LIKE 'Threads%'; +DESCRIBE users; +EXPLAIN SELECT * FROM users WHERE id = 1; +EXPLAIN FORMAT=JSON SELECT * FROM orders WHERE user_id = 1; diff --git a/testdata/realworld/mariadb/03_ddl_migrations.sql b/testdata/realworld/mariadb/03_ddl_migrations.sql new file mode 100644 index 00000000..cf127753 --- /dev/null +++ b/testdata/realworld/mariadb/03_ddl_migrations.sql @@ -0,0 +1,55 @@ +-- Schema migrations: CREATE/ALTER/DROP with IF [NOT] EXISTS, indexes, constraints +CREATE DATABASE IF NOT EXISTS shop CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +CREATE TABLE IF NOT EXISTS users (id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, email VARCHAR(255) NOT NULL, name VARCHAR(100) DEFAULT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_email (email)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE IF NOT EXISTS orders (id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, user_id BIGINT UNSIGNED NOT NULL, total DECIMAL(12, 2) NOT NULL DEFAULT 0.00, status ENUM('new', 'paid', 'shipped', 'cancelled') NOT NULL DEFAULT 'new', note TEXT, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user (user_id), KEY idx_status_created (status, created_at), CONSTRAINT fk_orders_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE ON UPDATE RESTRICT) ENGINE=InnoDB; +CREATE TABLE IF NOT EXISTS order_items (order_id BIGINT UNSIGNED NOT NULL, product_id INT NOT NULL, qty SMALLINT NOT NULL DEFAULT 1, price DECIMAL(10, 2) NOT NULL, PRIMARY KEY (order_id, product_id), CONSTRAINT chk_qty CHECK (qty > 0)); +CREATE TABLE IF NOT EXISTS audit_log (id BIGINT AUTO_INCREMENT PRIMARY KEY, actor VARCHAR(64) NOT NULL, action VARCHAR(32) NOT NULL, payload JSON, at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)) ENGINE=InnoDB ROW_FORMAT=DYNAMIC; +CREATE TABLE IF NOT EXISTS events (id BIGINT NOT NULL, happened_on DATE NOT NULL, body TEXT, PRIMARY KEY (id, happened_on)) PARTITION BY RANGE (YEAR(happened_on)) (PARTITION p2024 VALUES LESS THAN (2025), PARTITION p2025 VALUES LESS THAN (2026), PARTITION pmax VALUES LESS THAN MAXVALUE); +CREATE TABLE IF NOT EXISTS people (id INT PRIMARY KEY, first_name VARCHAR(50), last_name VARCHAR(50), full_name VARCHAR(101) AS (CONCAT(first_name, ' ', last_name)) VIRTUAL, name_len INT AS (CHAR_LENGTH(first_name)) PERSISTENT); +CREATE TABLE IF NOT EXISTS temp_copy LIKE users; +CREATE TABLE IF NOT EXISTS user_emails AS SELECT id, email FROM users WHERE email IS NOT NULL; +CREATE TEMPORARY TABLE IF NOT EXISTS tmp_ids (id INT PRIMARY KEY); +CREATE OR REPLACE TABLE scratch (a INT, b VARCHAR(10)); +CREATE INDEX IF NOT EXISTS idx_orders_total ON orders (total); +CREATE UNIQUE INDEX IF NOT EXISTS uk_users_name ON users (name, email); +CREATE FULLTEXT INDEX IF NOT EXISTS ft_note ON orders (note); +CREATE INDEX idx_prefix ON users (email(20)); +CREATE OR REPLACE VIEW v_active_users AS SELECT id, email FROM users WHERE name IS NOT NULL; +CREATE VIEW IF NOT EXISTS v_order_totals AS SELECT user_id, SUM(total) AS total FROM orders GROUP BY user_id; +ALTER TABLE users ADD COLUMN IF NOT EXISTS phone VARCHAR(32) NULL AFTER name; +ALTER TABLE users ADD COLUMN last_login DATETIME NULL, ADD COLUMN login_count INT NOT NULL DEFAULT 0; +ALTER TABLE users MODIFY COLUMN name VARCHAR(200) NOT NULL DEFAULT ''; +ALTER TABLE users CHANGE COLUMN phone mobile VARCHAR(40) NULL; +ALTER TABLE users DROP COLUMN IF EXISTS mobile; +ALTER TABLE users ADD INDEX IF NOT EXISTS idx_name (name); +ALTER TABLE users DROP INDEX IF EXISTS idx_name; +ALTER TABLE orders ADD CONSTRAINT fk_o_u FOREIGN KEY (user_id) REFERENCES users (id); +ALTER TABLE orders DROP FOREIGN KEY fk_o_u; +ALTER TABLE orders ADD PRIMARY KEY (id); +ALTER TABLE orders ENGINE=InnoDB, ALGORITHM=INPLACE, LOCK=NONE; +ALTER TABLE orders RENAME TO orders_old; +ALTER TABLE orders ALTER COLUMN status SET DEFAULT 'new'; +ALTER TABLE orders ALTER COLUMN note DROP DEFAULT; +ALTER ONLINE TABLE orders ADD COLUMN flag TINYINT(1) NOT NULL DEFAULT 0; +RENAME TABLE orders_old TO orders_archive, scratch TO scratch_old; +TRUNCATE TABLE audit_log; +DROP TABLE IF EXISTS scratch_old, temp_copy; +DROP VIEW IF EXISTS v_active_users; +DROP INDEX IF EXISTS idx_orders_total ON orders; +DROP DATABASE IF EXISTS shop_old; +CREATE TRIGGER trg_users_bu BEFORE UPDATE ON users FOR EACH ROW SET NEW.updated_at = NOW(); +CREATE OR REPLACE TRIGGER trg_orders_ai AFTER INSERT ON orders FOR EACH ROW INSERT INTO audit_log (actor, action) VALUES ('system', 'order'); +DROP TRIGGER IF EXISTS trg_users_bu; +CREATE USER IF NOT EXISTS 'app'@'%' IDENTIFIED BY 'secret'; +GRANT SELECT, INSERT, UPDATE, DELETE ON shop.* TO 'app'@'%'; +REVOKE DELETE ON shop.* FROM 'app'@'%'; +FLUSH PRIVILEGES; +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; +SET @a := 1; +SET SESSION sql_mode = 'STRICT_ALL_TABLES'; +USE shop; +ANALYZE TABLE orders; +OPTIMIZE TABLE orders; +LOCK TABLES orders WRITE, users READ; +UNLOCK TABLES; diff --git a/testdata/realworld/mariadb/04_window_analytics.sql b/testdata/realworld/mariadb/04_window_analytics.sql new file mode 100644 index 00000000..bda418e8 --- /dev/null +++ b/testdata/realworld/mariadb/04_window_analytics.sql @@ -0,0 +1,24 @@ +-- Analytics with window functions, CTEs and recursive CTEs +SELECT id, user_id, total, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at) AS rn FROM orders; +SELECT id, RANK() OVER (ORDER BY total DESC) AS r, DENSE_RANK() OVER (ORDER BY total DESC) AS dr FROM orders; +SELECT id, SUM(total) OVER (PARTITION BY user_id ORDER BY created_at ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running FROM orders; +SELECT id, AVG(total) OVER (ORDER BY created_at ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING) AS ma5 FROM orders; +SELECT id, SUM(total) OVER (ORDER BY created_at RANGE BETWEEN INTERVAL 7 DAY PRECEDING AND CURRENT ROW) FROM orders; +SELECT id, LAG(total, 1, 0) OVER (PARTITION BY user_id ORDER BY created_at) AS prev, LEAD(total) OVER (PARTITION BY user_id ORDER BY created_at) AS next FROM orders; +SELECT id, FIRST_VALUE(total) OVER w AS f, LAST_VALUE(total) OVER w AS l, NTH_VALUE(total, 2) OVER w AS n2 FROM orders WINDOW w AS (PARTITION BY user_id ORDER BY created_at ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING); +SELECT id, NTILE(4) OVER (ORDER BY total) AS quartile, PERCENT_RANK() OVER (ORDER BY total) AS pr, CUME_DIST() OVER (ORDER BY total) AS cd FROM orders; +SELECT DISTINCT user_id, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY total) OVER (PARTITION BY user_id) AS median FROM orders; +SELECT user_id, MEDIAN(total) OVER (PARTITION BY user_id) FROM orders; +SELECT * FROM (SELECT o.*, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn FROM orders o) t WHERE rn = 1; +WITH RECURSIVE nums (n) AS (SELECT 1 UNION ALL SELECT n + 1 FROM nums WHERE n < 100) SELECT n FROM nums; +WITH RECURSIVE tree AS (SELECT id, parent_id, name, 0 AS depth, CAST(name AS CHAR(1000)) AS path FROM categories WHERE parent_id IS NULL UNION ALL SELECT c.id, c.parent_id, c.name, t.depth + 1, CONCAT(t.path, '/', c.name) FROM categories c JOIN tree t ON c.parent_id = t.id) SELECT * FROM tree ORDER BY path; +WITH RECURSIVE org AS (SELECT id, manager_id, name FROM employees WHERE id = 1 UNION SELECT e.id, e.manager_id, e.name FROM employees e INNER JOIN org o ON e.manager_id = o.id) SELECT * FROM org; +WITH RECURSIVE dates AS (SELECT DATE('2025-01-01') AS d UNION ALL SELECT d + INTERVAL 1 DAY FROM dates WHERE d < '2025-12-31') SELECT d, COUNT(o.id) FROM dates LEFT JOIN orders o ON DATE(o.created_at) = d GROUP BY d; +WITH first_orders AS (SELECT user_id, MIN(created_at) AS first_at FROM orders GROUP BY user_id), cohorts AS (SELECT user_id, DATE_FORMAT(first_at, '%Y-%m') AS cohort FROM first_orders), activity AS (SELECT c.cohort, DATE_FORMAT(o.created_at, '%Y-%m') AS active_month, COUNT(DISTINCT o.user_id) AS users FROM cohorts c JOIN orders o ON o.user_id = c.user_id GROUP BY c.cohort, active_month) SELECT cohort, active_month, users, FIRST_VALUE(users) OVER (PARTITION BY cohort ORDER BY active_month) AS cohort_size FROM activity ORDER BY cohort, active_month; +WITH t AS (SELECT 1 AS x) SELECT * FROM t; +WITH s1 AS (SELECT user_id, SUM(total) AS s FROM orders GROUP BY user_id), s2 AS (SELECT user_id, s, NTILE(10) OVER (ORDER BY s DESC) AS decile FROM s1), s3 AS (SELECT decile, COUNT(*) AS n, AVG(s) AS avg_s FROM s2 GROUP BY decile) SELECT * FROM s3 ORDER BY decile; +SELECT user_id, total, total / SUM(total) OVER (PARTITION BY user_id) AS share FROM orders; +SELECT COUNT(*) OVER () AS total_rows, id FROM orders LIMIT 10; +SELECT id, SUM(total) OVER (PARTITION BY user_id ORDER BY created_at ROWS UNBOUNDED PRECEDING) FROM orders; +SELECT a.id, b.id FROM (SELECT id FROM t1) a, (SELECT id FROM t2) b WHERE a.id < b.id; +SELECT id, CASE WHEN ROW_NUMBER() OVER (ORDER BY id) % 2 = 0 THEN 'even' ELSE 'odd' END FROM t1; diff --git a/testdata/realworld/mariadb/05_mariadb_specific.sql b/testdata/realworld/mariadb/05_mariadb_specific.sql new file mode 100644 index 00000000..d2d608e7 --- /dev/null +++ b/testdata/realworld/mariadb/05_mariadb_specific.sql @@ -0,0 +1,71 @@ +-- MariaDB specific: SEQUENCE, RETURNING, INTERSECT/EXCEPT, system versioning, invisible columns, JSON_TABLE +CREATE SEQUENCE IF NOT EXISTS seq_invoice START WITH 1000 INCREMENT BY 1 MINVALUE 1 MAXVALUE 99999999 CACHE 100 NOCYCLE; +CREATE OR REPLACE SEQUENCE seq_ticket START WITH 1 INCREMENT BY 5 CYCLE; +CREATE SEQUENCE seq_simple; +ALTER SEQUENCE seq_invoice RESTART WITH 5000; +ALTER SEQUENCE IF EXISTS seq_invoice INCREMENT BY 10; +DROP SEQUENCE IF EXISTS seq_ticket; +SELECT NEXT VALUE FOR seq_invoice; +SELECT NEXTVAL(seq_invoice), LASTVAL(seq_invoice); +SELECT SETVAL(seq_invoice, 7000); +INSERT INTO invoices (id, amount) VALUES (NEXT VALUE FOR seq_invoice, 99.50); +INSERT INTO invoices (id, amount) VALUES (NEXTVAL(seq_invoice), 10); +CREATE TABLE IF NOT EXISTS tickets (id BIGINT NOT NULL DEFAULT NEXT VALUE FOR seq_ticket PRIMARY KEY, title VARCHAR(100)); +INSERT INTO users (email, name) VALUES ('r@example.com', 'R') RETURNING id, created_at; +INSERT INTO users (email, name) VALUES ('s@example.com', 'S'), ('t@example.com', 'T') RETURNING *; +INSERT INTO users (email, name) SELECT email, name FROM staging_users RETURNING id AS new_id, email; +DELETE FROM sessions WHERE expires_at < NOW() RETURNING id, user_id; +DELETE FROM carts WHERE user_id = 5 RETURNING *; +REPLACE INTO settings (k, v) VALUES ('a', '1') RETURNING k, v; +INSERT INTO page_views (page_id, views) VALUES (1, 1) ON DUPLICATE KEY UPDATE views = views + 1 RETURNING page_id, views; +SELECT id FROM t1 INTERSECT SELECT id FROM t2; +SELECT id FROM t1 EXCEPT SELECT id FROM t2; +SELECT id FROM t1 INTERSECT ALL SELECT id FROM t2; +SELECT id FROM t1 EXCEPT ALL SELECT id FROM t2; +(SELECT id FROM t1 UNION SELECT id FROM t2) INTERSECT SELECT id FROM t3; +SELECT id FROM t1 UNION SELECT id FROM t2 EXCEPT SELECT id FROM t3 ORDER BY id; +SELECT user_id FROM orders WHERE status = 'paid' INTERSECT SELECT user_id FROM orders WHERE status = 'shipped' EXCEPT SELECT user_id FROM bans; +CREATE TABLE IF NOT EXISTS accounts (id INT PRIMARY KEY, balance DECIMAL(12, 2) NOT NULL) WITH SYSTEM VERSIONING; +CREATE TABLE IF NOT EXISTS prices (id INT PRIMARY KEY, price DECIMAL(10, 2), row_start TIMESTAMP(6) GENERATED ALWAYS AS ROW START, row_end TIMESTAMP(6) GENERATED ALWAYS AS ROW END, PERIOD FOR SYSTEM_TIME (row_start, row_end)) WITH SYSTEM VERSIONING; +CREATE TABLE IF NOT EXISTS hist (id INT, v INT) WITH SYSTEM VERSIONING PARTITION BY SYSTEM_TIME INTERVAL 1 MONTH (PARTITION p_old HISTORY, PARTITION p_cur CURRENT); +ALTER TABLE users ADD SYSTEM VERSIONING; +ALTER TABLE users DROP SYSTEM VERSIONING; +ALTER TABLE users ADD COLUMN IF NOT EXISTS secret VARCHAR(64) INVISIBLE; +CREATE TABLE IF NOT EXISTS inv (id INT PRIMARY KEY, a INT, hidden_note TEXT INVISIBLE); +ALTER TABLE users ADD COLUMN IF NOT EXISTS email_hash CHAR(32) WITHOUT SYSTEM VERSIONING; +SELECT * FROM accounts FOR SYSTEM_TIME AS OF TIMESTAMP '2025-01-01 00:00:00' WHERE id = 1; +SELECT * FROM accounts FOR SYSTEM_TIME BETWEEN '2025-01-01' AND '2025-06-30' WHERE id = 1; +SELECT * FROM accounts FOR SYSTEM_TIME FROM '2025-01-01' TO '2025-02-01'; +SELECT * FROM accounts FOR SYSTEM_TIME ALL ORDER BY id; +SELECT id, ROW_START, ROW_END FROM accounts FOR SYSTEM_TIME ALL WHERE id = 1; +DELETE HISTORY FROM accounts BEFORE SYSTEM_TIME '2024-01-01 00:00:00'; +SELECT jt.* FROM JSON_TABLE('[{"a":1,"b":"x"},{"a":2,"b":"y"}]', '$[*]' COLUMNS (a INT PATH '$.a', b VARCHAR(10) PATH '$.b')) AS jt; +SELECT o.id, jt.sku, jt.qty FROM orders o, JSON_TABLE(o.items, '$[*]' COLUMNS (rn FOR ORDINALITY, sku VARCHAR(32) PATH '$.sku', qty INT PATH '$.qty' DEFAULT '1' ON EMPTY)) AS jt; +SELECT JSON_EXTRACT(payload, '$.user.id') AS uid, JSON_UNQUOTE(JSON_EXTRACT(payload, '$.name')) FROM audit_log; +SELECT JSON_VALUE(payload, '$.name') AS n, JSON_QUERY(payload, '$.tags') AS t FROM audit_log WHERE JSON_VALID(payload); +SELECT JSON_OBJECT('id', id, 'email', email) FROM users; +SELECT JSON_ARRAYAGG(email), JSON_OBJECTAGG(id, email) FROM users; +SELECT JSON_SET(payload, '$.seen', TRUE), JSON_ARRAY_APPEND(payload, '$.tags', 'x') FROM audit_log; +SELECT * FROM audit_log WHERE JSON_CONTAINS(payload, '"x"', '$.tags') AND JSON_LENGTH(payload) > 1; +SELECT UUID(), UUID_SHORT(); +CREATE TABLE IF NOT EXISTS things (id UUID NOT NULL DEFAULT UUID() PRIMARY KEY, addr INET6, ip4 INET4); +SELECT SYS_GUID(); +SELECT id, COLUMN_GET(dyn, 'k' AS CHAR) FROM dyn_t; +SELECT REGEXP_REPLACE(name, '[0-9]+', '#'), REGEXP_SUBSTR(name, '[a-z]+'), REGEXP_INSTR(name, 'x') FROM users; +SELECT LEFT(name, 3), RIGHT(name, 2), SUBSTRING_INDEX(email, '@', -1), TRIM(BOTH ' ' FROM name), LPAD(id, 8, '0') FROM users; +SELECT DATE_FORMAT(NOW(), '%Y-%m-%d'), STR_TO_DATE('2025-01-02', '%Y-%m-%d'), UNIX_TIMESTAMP(), FROM_UNIXTIME(0); +SELECT * FROM information_schema.tables WHERE table_schema = DATABASE(); +SELECT * FROM mysql.user LIMIT 1; +SET STATEMENT max_statement_time = 5 FOR SELECT * FROM orders; +SELECT * FROM orders ORDER BY id LIMIT 10 OFFSET 5 ROWS EXAMINED 1000; +SELECT * FROM orders FETCH FIRST 5 ROWS ONLY; +SELECT * FROM orders ORDER BY id OFFSET 10 ROWS FETCH NEXT 5 ROWS ONLY; +SELECT * FROM orders ORDER BY id FETCH FIRST 3 ROWS WITH TIES; +SELECT id FROM orders WHERE id > 5 LIMIT 5, 10; +SELECT * FROM t1 FOR UPDATE WAIT 5; +SELECT * FROM t1 FOR UPDATE NOWAIT; +CREATE ROLE IF NOT EXISTS reporting; +GRANT SELECT ON shop.* TO reporting; +SET ROLE reporting; +BACKUP STAGE START; +BACKUP STAGE END; diff --git a/testdata/realworld/mariadb/06_etl_admin.sql b/testdata/realworld/mariadb/06_etl_admin.sql new file mode 100644 index 00000000..3f362ee1 --- /dev/null +++ b/testdata/realworld/mariadb/06_etl_admin.sql @@ -0,0 +1,42 @@ +-- ETL batch loads, data cleanup and DBA administration queries +INSERT INTO dim_customer (customer_id, email, segment) SELECT u.id, LOWER(TRIM(u.email)), CASE WHEN s.total > 1000 THEN 'gold' ELSE 'std' END FROM users u LEFT JOIN (SELECT user_id, SUM(total) AS total FROM orders GROUP BY user_id) s ON s.user_id = u.id ON DUPLICATE KEY UPDATE email = VALUES(email), segment = VALUES(segment); +INSERT INTO daily_sales (day, revenue, orders) SELECT DATE(created_at), SUM(total), COUNT(*) FROM orders WHERE created_at >= CURDATE() - INTERVAL 1 DAY AND created_at < CURDATE() GROUP BY DATE(created_at); +UPDATE dim_customer c JOIN (SELECT user_id, MAX(created_at) AS last_order FROM orders GROUP BY user_id) o ON o.user_id = c.customer_id SET c.last_order = o.last_order; +UPDATE orders SET status = CASE id WHEN 1 THEN 'paid' WHEN 2 THEN 'shipped' ELSE status END WHERE id IN (1, 2, 3); +UPDATE users SET email = LOWER(email), name = TRIM(name) WHERE email <> LOWER(email); +UPDATE t1, t2 SET t1.a = t2.b WHERE t1.id = t2.id; +UPDATE IGNORE products SET sku = UPPER(sku); +DELETE FROM audit_log WHERE at < NOW() - INTERVAL 90 DAY LIMIT 10000; +DELETE t1 FROM t1 INNER JOIN t2 ON t1.id = t2.id WHERE t2.flag = 1; +DELETE FROM t1 USING t1 INNER JOIN t2 ON t1.id = t2.id; +INSERT INTO archive.orders SELECT * FROM orders WHERE created_at < '2023-01-01'; +INSERT INTO ledger (account, amount) VALUES (1, -5.00), (2, 5.00); +INSERT INTO t (a, b) VALUES (1, DEFAULT), (DEFAULT, 2); +INSERT INTO t () VALUES (); +INSERT INTO t1 (a) VALUES (1) ON DUPLICATE KEY UPDATE a = a + VALUES(a), b = IF(b IS NULL, 1, b); +LOAD DATA INFILE '/tmp/users.csv' INTO TABLE users FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' LINES TERMINATED BY '\n' IGNORE 1 LINES (email, name); +SELECT * FROM orders INTO OUTFILE '/tmp/orders.csv' FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n'; +SELECT table_schema, SUM(data_length + index_length) / 1024 / 1024 AS size_mb FROM information_schema.tables GROUP BY table_schema ORDER BY size_mb DESC; +SELECT table_name, engine, table_rows FROM information_schema.tables WHERE table_schema = 'shop' AND table_type = 'BASE TABLE'; +SELECT column_name, data_type, is_nullable FROM information_schema.columns WHERE table_schema = 'shop' AND table_name = 'users' ORDER BY ordinal_position; +SELECT constraint_name, referenced_table_name FROM information_schema.key_column_usage WHERE table_schema = DATABASE() AND referenced_table_name IS NOT NULL; +SELECT id, user, host, db, command, time, state, info FROM information_schema.processlist WHERE command <> 'Sleep' AND time > 10 ORDER BY time DESC; +SELECT @@global.max_connections, @@session.sql_mode, @@version, @@innodb_buffer_pool_size; +SELECT * FROM mysql.slow_log ORDER BY start_time DESC LIMIT 50; +KILL 1234; +KILL QUERY 1234; +SELECT t.table_name FROM information_schema.tables t LEFT JOIN information_schema.statistics s ON s.table_schema = t.table_schema AND s.table_name = t.table_name WHERE s.index_name IS NULL AND t.table_schema = 'shop'; +SELECT COUNT(*) AS dupes, email FROM users GROUP BY email HAVING COUNT(*) > 1; +SELECT u1.id, u2.id FROM users u1 JOIN users u2 ON u1.email = u2.email AND u1.id < u2.id; +SELECT * FROM orders o WHERE o.total > (SELECT AVG(total) FROM orders WHERE user_id = o.user_id); +SELECT * FROM orders WHERE (user_id, created_at) IN (SELECT user_id, MAX(created_at) FROM orders GROUP BY user_id); +SELECT * FROM products WHERE price > ALL (SELECT price FROM products WHERE category_id = 2); +SELECT * FROM products WHERE price > ANY (SELECT price FROM products WHERE category_id = 2); +SELECT * FROM users WHERE id = (SELECT MAX(id) FROM users); +SELECT 1 FROM DUAL; +SELECT NULL, TRUE, FALSE, 0x1F, X'1F', b'101', 1.5e3, .5, 'it''s', "dq"; +SELECT _utf8mb4'abc' COLLATE utf8mb4_bin, BINARY name, name COLLATE utf8mb4_general_ci FROM users; +SELECT @rownum := @rownum + 1 AS rn, t.* FROM t, (SELECT @rownum := 0) r; +PREPARE stmt FROM 'SELECT * FROM users WHERE id = ?'; +EXECUTE stmt USING @id; +DEALLOCATE PREPARE stmt; diff --git a/testdata/realworld/mariadb/07_stored_programs.sql b/testdata/realworld/mariadb/07_stored_programs.sql new file mode 100644 index 00000000..094265c5 --- /dev/null +++ b/testdata/realworld/mariadb/07_stored_programs.sql @@ -0,0 +1,20 @@ +-- Stored procedures, functions, events and transactional patterns +CREATE PROCEDURE sp_touch_user(IN p_id BIGINT) UPDATE users SET updated_at = NOW() WHERE id = p_id; +CREATE OR REPLACE FUNCTION fn_double(x INT) RETURNS INT DETERMINISTIC RETURN x * 2; +CREATE FUNCTION IF NOT EXISTS fn_tax(amount DECIMAL(10, 2)) RETURNS DECIMAL(10, 2) DETERMINISTIC RETURN amount * 0.2; +DROP PROCEDURE IF EXISTS sp_touch_user; +DROP FUNCTION IF EXISTS fn_double; +CALL sp_touch_user(42); +CREATE EVENT IF NOT EXISTS ev_purge ON SCHEDULE EVERY 1 DAY STARTS '2025-01-01 03:00:00' DO DELETE FROM sessions WHERE expires_at < NOW(); +DROP EVENT IF EXISTS ev_purge; +BEGIN; +ROLLBACK; +SAVEPOINT sp1; +ROLLBACK TO SAVEPOINT sp1; +RELEASE SAVEPOINT sp1; +SET autocommit = 0; +SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +START TRANSACTION READ ONLY; +START TRANSACTION WITH CONSISTENT SNAPSHOT; +XA START 'x1'; +CREATE DEFINER = 'root'@'localhost' PROCEDURE sp_purge() DELETE FROM sessions WHERE expires_at < NOW(); diff --git a/testdata/realworld/mariadb/08_complex_queries.sql b/testdata/realworld/mariadb/08_complex_queries.sql new file mode 100644 index 00000000..6fdf3dd6 --- /dev/null +++ b/testdata/realworld/mariadb/08_complex_queries.sql @@ -0,0 +1,32 @@ +-- Deeply nested and long-join analytical queries as produced by BI tools +SELECT a.id, a.name, b.total, c.cnt FROM accounts a INNER JOIN (SELECT account_id, SUM(amount) AS total FROM ledger WHERE posted_at BETWEEN '2025-01-01' AND '2025-12-31' GROUP BY account_id HAVING SUM(amount) <> 0) b ON b.account_id = a.id LEFT JOIN (SELECT account_id, COUNT(*) AS cnt FROM tickets WHERE status IN ('open', 'pending') GROUP BY account_id) c ON c.account_id = a.id WHERE a.active = 1 AND a.id NOT IN (SELECT account_id FROM blocked) ORDER BY b.total DESC, a.name ASC LIMIT 50; +SELECT * FROM (SELECT * FROM (SELECT * FROM (SELECT id, v FROM t WHERE v > 0) l3 WHERE id > 1) l2 WHERE id < 100) l1 ORDER BY id; +SELECT x.id FROM t1 x WHERE x.id IN (SELECT y.id FROM t2 y WHERE y.v > (SELECT AVG(z.v) FROM t3 z WHERE z.k IN (SELECT w.k FROM t4 w WHERE EXISTS (SELECT 1 FROM t5 q WHERE q.w = w.id)))); +SELECT p.id, p.name, c.name AS category, b.name AS brand, s.name AS supplier, w.name AS warehouse, i.qty, pr.price, cur.code FROM products p JOIN categories c ON c.id = p.category_id JOIN brands b ON b.id = p.brand_id JOIN suppliers s ON s.id = p.supplier_id JOIN inventory i ON i.product_id = p.id JOIN warehouses w ON w.id = i.warehouse_id JOIN prices pr ON pr.product_id = p.id AND pr.valid_to IS NULL JOIN currencies cur ON cur.id = pr.currency_id WHERE i.qty > 0 AND p.active; +SELECT CASE WHEN a > 0 THEN CASE WHEN b > 0 THEN 'pp' ELSE 'pn' END ELSE CASE WHEN b > 0 THEN 'np' ELSE 'nn' END END AS quad, COUNT(*) FROM pts GROUP BY quad; +SELECT ((((a + b) * c) - d) / NULLIF(e, 0)) AS r, -a AS neg, +b AS pos, NOT (a > b AND (c < d OR e = f)) AS flag FROM nums; +SELECT * FROM t WHERE a = 1 AND (b = 2 OR c = 3) AND NOT (d = 4 OR e = 5) AND f IN (1, 2, 3) AND g BETWEEN 1 AND 10 AND h LIKE 'x%' AND i IS NULL; +SELECT COALESCE((SELECT MAX(v) FROM t2 WHERE t2.id = t1.id), (SELECT MIN(v) FROM t3 WHERE t3.id = t1.id), 0) FROM t1; +SELECT u.id, (SELECT GROUP_CONCAT(r.name SEPARATOR ',') FROM roles r JOIN user_roles ur ON ur.role_id = r.id WHERE ur.user_id = u.id) AS roles FROM users u; +SELECT t.id, t.n FROM (SELECT id, COUNT(*) AS n FROM events GROUP BY id) AS t JOIN (SELECT id FROM users WHERE active = 1) AS u USING (id) WHERE t.n > 5; +SELECT a.x, b.y FROM a JOIN b ON a.id = b.id AND (a.k = b.k OR (a.k IS NULL AND b.k IS NULL)); +SELECT * FROM a LEFT JOIN b ON a.id = b.a_id LEFT JOIN c ON b.id = c.b_id RIGHT JOIN d ON d.id = c.d_id; +SELECT * FROM a JOIN (b JOIN c ON b.id = c.b_id) ON a.id = b.a_id; +SELECT * FROM a, b, c WHERE a.id = b.id AND b.id = c.id; +SELECT DISTINCT a.x FROM a WHERE a.y > 1 ORDER BY a.x LIMIT 5; +SELECT COUNT(DISTINCT a, b) FROM t; +SELECT SUM(IF(status = 'paid', total, 0)) AS paid, SUM(IF(status = 'new', total, 0)) AS pending FROM orders; +SELECT user_id, MAX(CASE WHEN k = 'a' THEN v END) AS a, MAX(CASE WHEN k = 'b' THEN v END) AS b FROM kv GROUP BY user_id; +SELECT a, b, COUNT(*) FROM t GROUP BY a, b WITH ROLLUP HAVING COUNT(*) > 1 ORDER BY a DESC, b; +SELECT * FROM t1 WHERE id = ANY (SELECT id FROM t2) ORDER BY RAND() LIMIT 1; +SELECT ROUND(AVG(total), 2), FLOOR(MAX(total)), CEIL(MIN(total)), ABS(SUM(total)), POWER(2, 10), SQRT(16), MOD(10, 3) FROM orders; +VALUES (1, 'a'), (2, 'b'); +SELECT * FROM t1 WHERE a IN (SELECT a FROM t2 UNION SELECT a FROM t3); +SELECT * FROM (SELECT a FROM t1 UNION ALL SELECT a FROM t2) u ORDER BY a; +SELECT (SELECT 1) + (SELECT 2); +SELECT EXISTS (SELECT 1 FROM t WHERE a = 1); +SELECT id, name FROM users WHERE MATCH (name, bio) AGAINST ('database' IN NATURAL LANGUAGE MODE); +SELECT id FROM docs WHERE MATCH (body) AGAINST ('+sql -nosql' IN BOOLEAN MODE); +SELECT ST_AsText(ST_GeomFromText('POINT(1 1)')), ST_Distance(a.pt, b.pt) FROM a, b; +SELECT INTERVAL(5, 1, 3, 7), FIELD('b', 'a', 'b'), ELT(2, 'x', 'y'), FIND_IN_SET('a', 'a,b'); +SELECT BIT_AND(a), BIT_OR(a), BIT_XOR(a), VARIANCE(a), STD(a) FROM t; diff --git a/testdata/realworld/mariadb/09_mysql_compat_misc.sql b/testdata/realworld/mariadb/09_mysql_compat_misc.sql new file mode 100644 index 00000000..ecc82c5e --- /dev/null +++ b/testdata/realworld/mariadb/09_mysql_compat_misc.sql @@ -0,0 +1,57 @@ +-- Misc MySQL-compatible statements seen in migration tools, monitoring agents and ORMs (Django, Rails, Laravel) +SELECT @@version_comment LIMIT 1; +SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'shop' AND TABLE_NAME = 'users' LIMIT 1; +SELECT `users`.* FROM `users` WHERE `users`.`deleted_at` IS NULL ORDER BY `users`.`id` ASC LIMIT 1; +SELECT COUNT(*) AS `count` FROM `orders` WHERE `orders`.`user_id` = 5; +SELECT `orders`.`id` AS t0_r0, `users`.`id` AS t1_r0 FROM `orders` LEFT OUTER JOIN `users` ON `users`.`id` = `orders`.`user_id`; +SELECT 1 AS one FROM `users` WHERE `users`.`email` = BINARY 'a@b.c' LIMIT 1; +INSERT INTO `django_migrations` (`app`, `name`, `applied`) VALUES ('auth', '0001_initial', '2025-01-01 00:00:00.000000'); +UPDATE `users` SET `last_login` = '2025-01-01 00:00:00.000000' WHERE `users`.`id` = 1; +DELETE FROM `django_session` WHERE `django_session`.`expire_date` < '2025-01-01 00:00:00.000000'; +SELECT `a`.`id`, `a`.`title` FROM `articles` `a` WHERE `a`.`id` IN (1, 2, 3) AND `a`.`status` = 'published'; +SELECT SQL_NO_CACHE * FROM t1; +SELECT DISTINCTROW a FROM t1; +SELECT HIGH_PRIORITY a FROM t1; +SELECT ALL a FROM t1; +SELECT a FROM t1 WHERE b = 'x' COLLATE utf8mb4_bin; +SELECT CURRENT_TIMESTAMP, CURRENT_DATE, CURRENT_TIME, LOCALTIME, NOW(6), UTC_TIMESTAMP(); +SELECT EXTRACT(YEAR FROM created_at), EXTRACT(MONTH FROM created_at) FROM orders; +SELECT SUBSTRING(name FROM 2 FOR 3), POSITION('a' IN name), TRIM(LEADING '0' FROM code) FROM t; +SELECT CHAR_LENGTH(name), LENGTH(name), REVERSE(name), REPEAT('x', 3), REPLACE(name, 'a', 'b'), INSTR(name, 'a'), LOCATE('a', name) FROM t; +SELECT MD5(email), SHA1(email), SHA2(email, 256), HEX(name), UNHEX('41'), TO_BASE64(name), FROM_BASE64('QQ==') FROM users; +SELECT AES_ENCRYPT('x', 'k'), AES_DECRYPT(col, 'k'), COMPRESS(col), UNCOMPRESS(col) FROM t; +SELECT INET_ATON('1.2.3.4'), INET_NTOA(16909060), INET6_ATON('::1'); +SELECT IFNULL(a, 0) + IFNULL(b, 0), NULLIF(a, 0), GREATEST(a, b, c), LEAST(a, b, c) FROM t; +SELECT GET_LOCK('k', 10), RELEASE_LOCK('k'), IS_FREE_LOCK('k'); +SELECT SLEEP(1), BENCHMARK(1000, MD5('x')), CONNECTION_ID(), ROW_COUNT(); +SELECT a FROM t1 WHERE a > 1 UNION DISTINCT SELECT a FROM t2; +SELECT a, b FROM t1 ORDER BY a ASC, b DESC LIMIT 3 OFFSET 2; +SELECT * FROM t1 WHERE a = 1 ORDER BY 1, 2; +SELECT t1.*, t2.* FROM t1, t2; +SELECT db1.t1.col FROM db1.t1; +SELECT `my table`.`my col` FROM `my table`; +SELECT * FROM t1 WHERE a = -1 AND b = - 1 AND c = +1; +SELECT 1 FROM t1 WHERE a IN (SELECT 1) AND b NOT BETWEEN 1 AND 2 AND c NOT LIKE 'x' AND d NOT IN (1, 2); +SELECT a FROM t1 WHERE a SOUNDS LIKE 'abc'; +SELECT a FROM t1 WHERE a RLIKE '^x'; +SELECT a FROM t1 WHERE NOT EXISTS (SELECT 1 FROM t2 WHERE t2.a = t1.a); +SELECT a FROM t1 WHERE a IS TRUE AND b IS NOT FALSE AND c IS UNKNOWN; +SELECT a & b, a | b, a ^ b, a << 1, a >> 1 FROM t1; +SHOW CREATE VIEW v_active_users; +SHOW TABLE STATUS LIKE 'orders'; +SHOW ENGINE INNODB STATUS; +SHOW MASTER STATUS; +SHOW SLAVE STATUS; +SHOW WARNINGS; +SHOW ERRORS; +SHOW DATABASES; +SHOW FULL TABLES FROM shop WHERE Table_type = 'BASE TABLE'; +SHOW BINARY LOGS; +SHOW CREATE SEQUENCE seq_invoice; +SHOW COLLATION LIKE 'utf8mb4%'; +SHOW GLOBAL STATUS LIKE 'Questions'; +EXPLAIN EXTENDED SELECT * FROM t1; +EXPLAIN SELECT * FROM t1 WHERE a = 1; +EXPLAIN DELETE FROM t1 WHERE a = 1; +ANALYZE SELECT * FROM t1 WHERE a = 1; +ANALYZE FORMAT=JSON SELECT * FROM t1; diff --git a/testdata/realworld/mysql/01_app_crud.sql b/testdata/realworld/mysql/01_app_crud.sql new file mode 100644 index 00000000..5d67fb03 --- /dev/null +++ b/testdata/realworld/mysql/01_app_crud.sql @@ -0,0 +1,32 @@ +-- ORM-style application CRUD (Laravel/Eloquent, Django, Sequelize generated) +SELECT * FROM `users` WHERE `id` = 1 LIMIT 1; +SELECT `id`, `name`, `email` FROM `users` WHERE `email` = 'a@example.com' AND `deleted_at` IS NULL LIMIT 1; +SELECT * FROM `users` WHERE `id` IN (1, 2, 3, 4) ORDER BY `created_at` DESC; +SELECT * FROM `posts` WHERE `user_id` = 5 ORDER BY `created_at` DESC LIMIT 20 OFFSET 40; +SELECT * FROM `posts` ORDER BY `id` DESC LIMIT 40, 20; +SELECT COUNT(*) AS `aggregate` FROM `posts` WHERE `status` = 'published'; +SELECT EXISTS(SELECT * FROM `users` WHERE `email` = 'a@example.com') AS `exists`; +INSERT INTO `users` (`name`, `email`, `password`, `created_at`, `updated_at`) VALUES ('Ann', 'ann@example.com', 'hash', NOW(), NOW()); +INSERT INTO `users` (`name`, `email`) VALUES ('A', 'a@x.com'), ('B', 'b@x.com'), ('C', 'c@x.com'); +INSERT INTO `user_settings` (`user_id`, `k`, `v`) VALUES (1, 'theme', 'dark') ON DUPLICATE KEY UPDATE `v` = VALUES(`v`); +INSERT INTO `page_views` (`page_id`, `views`) VALUES (10, 1) ON DUPLICATE KEY UPDATE `views` = `views` + 1; +INSERT IGNORE INTO `tags` (`name`) VALUES ('go'), ('sql'); +REPLACE INTO `sessions` (`id`, `payload`, `last_activity`) VALUES ('abc', 'data', 1700000000); +INSERT INTO `archive_orders` SELECT * FROM `orders` WHERE `created_at` < '2023-01-01'; +INSERT INTO `t1` SET `a` = 1, `b` = 'x'; +UPDATE `users` SET `name` = 'Bob', `updated_at` = NOW() WHERE `id` = 7; +UPDATE `posts` SET `views` = `views` + 1 WHERE `id` = 99; +UPDATE `orders` SET `status` = 'shipped' WHERE `id` IN (1, 2, 3) AND `status` = 'paid' LIMIT 100; +UPDATE `products` p JOIN `categories` c ON c.id = p.category_id SET p.active = 0 WHERE c.archived = 1; +UPDATE orders o, customers c SET o.region = c.region WHERE o.customer_id = c.id; +DELETE FROM `sessions` WHERE `last_activity` < UNIX_TIMESTAMP() - 7200; +DELETE FROM `comments` WHERE `post_id` = 3 ORDER BY `id` LIMIT 500; +DELETE o FROM orders o LEFT JOIN customers c ON c.id = o.customer_id WHERE c.id IS NULL; +DELETE FROM a, b USING a INNER JOIN b ON a.id = b.a_id WHERE a.flag = 0; +SELECT `u`.*, `p`.`title` FROM `users` AS `u` INNER JOIN `posts` AS `p` ON `p`.`user_id` = `u`.`id` WHERE `u`.`id` = 1; +SELECT `id` FROM `users` WHERE `id` NOT IN (SELECT `user_id` FROM `bans`); +SELECT * FROM `users` WHERE `created_at` BETWEEN '2024-01-01 00:00:00' AND '2024-12-31 23:59:59'; +SELECT * FROM `users` WHERE `name` LIKE '%son' OR `name` NOT LIKE 'A%'; +SELECT `id` FROM `users` WHERE `id` > 100 FOR UPDATE; +SELECT * FROM `jobs` WHERE `queue` = 'default' AND `reserved_at` IS NULL ORDER BY `id` LIMIT 1 FOR UPDATE SKIP LOCKED; +SELECT * FROM accounts WHERE id = 1 LOCK IN SHARE MODE; diff --git a/testdata/realworld/mysql/02_reporting.sql b/testdata/realworld/mysql/02_reporting.sql new file mode 100644 index 00000000..c1659b6e --- /dev/null +++ b/testdata/realworld/mysql/02_reporting.sql @@ -0,0 +1,36 @@ +-- Dashboard and reporting queries: aggregates, joins, subqueries +SELECT DATE(created_at) AS day, COUNT(*) AS orders, SUM(total) AS revenue FROM orders WHERE created_at >= NOW() - INTERVAL 30 DAY GROUP BY DATE(created_at) ORDER BY day; +SELECT YEAR(created_at) AS y, MONTH(created_at) AS m, SUM(total) FROM orders GROUP BY YEAR(created_at), MONTH(created_at) WITH ROLLUP; +SELECT DATE_FORMAT(created_at, '%Y-%m') AS ym, COUNT(DISTINCT customer_id) AS buyers FROM orders GROUP BY ym HAVING buyers > 10 ORDER BY ym DESC; +SELECT c.id, c.name, COUNT(o.id) AS n, COALESCE(SUM(o.total), 0) AS spent FROM customers c LEFT JOIN orders o ON o.customer_id = c.id GROUP BY c.id, c.name ORDER BY spent DESC LIMIT 10; +SELECT product_id, SUM(qty) AS sold FROM order_items GROUP BY product_id ORDER BY sold DESC LIMIT 5; +SELECT category, GROUP_CONCAT(name ORDER BY name SEPARATOR ', ') AS names FROM products GROUP BY category; +SELECT user_id, GROUP_CONCAT(DISTINCT role) FROM user_roles GROUP BY user_id; +SELECT * FROM orders WHERE total > (SELECT AVG(total) FROM orders); +SELECT p.name, (SELECT COUNT(*) FROM reviews r WHERE r.product_id = p.id) AS review_count FROM products p; +SELECT * FROM customers c WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id AND o.total > 1000); +SELECT * FROM customers c WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id); +SELECT t.day, t.cnt FROM (SELECT DATE(created_at) AS day, COUNT(*) AS cnt FROM visits GROUP BY DATE(created_at)) AS t WHERE t.cnt > 100; +SELECT status, COUNT(*) FROM orders GROUP BY status HAVING COUNT(*) > 5 ORDER BY COUNT(*) DESC; +SELECT CASE WHEN total < 50 THEN 'small' WHEN total < 500 THEN 'medium' ELSE 'large' END AS bucket, COUNT(*) FROM orders GROUP BY bucket; +SELECT SUM(IF(status = 'paid', total, 0)) AS paid, SUM(IF(status = 'refunded', total, 0)) AS refunded FROM orders; +SELECT IFNULL(region, 'unknown') AS region, COUNT(*) FROM customers GROUP BY region; +SELECT id, total, ROUND(total * 1.2, 2) AS gross FROM orders WHERE total BETWEEN 10 AND 100; +SELECT a.id FROM a UNION SELECT b.id FROM b UNION ALL SELECT c.id FROM c; +(SELECT id, 'order' AS kind FROM orders ORDER BY id DESC LIMIT 5) UNION ALL (SELECT id, 'refund' FROM refunds ORDER BY id DESC LIMIT 5); +SELECT DISTINCT country FROM customers WHERE country IS NOT NULL ORDER BY country; +SELECT TIMESTAMPDIFF(DAY, created_at, shipped_at) AS days_to_ship FROM orders WHERE shipped_at IS NOT NULL; +SELECT DATEDIFF(NOW(), last_login) AS idle FROM users HAVING idle > 90; +SELECT * FROM events WHERE ts >= DATE_SUB(NOW(), INTERVAL 1 HOUR); +SELECT * FROM events WHERE ts > NOW() - INTERVAL 15 MINUTE; +SELECT DATE_ADD(CURDATE(), INTERVAL 1 MONTH) AS next_month; +SELECT o.id, c.name, p.name FROM orders o JOIN customers c ON c.id = o.customer_id JOIN order_items i ON i.order_id = o.id JOIN products p ON p.id = i.product_id LEFT JOIN coupons cp ON cp.id = o.coupon_id WHERE o.status <> 'cancelled'; +SELECT a.x FROM a NATURAL JOIN b; +SELECT * FROM a JOIN b USING (id) JOIN c USING (id); +SELECT * FROM t1 LEFT JOIN (t2, t3) ON t1.id = t2.id AND t2.id = t3.id; +SELECT * FROM a CROSS JOIN b; +SELECT * FROM a STRAIGHT_JOIN b ON a.id = b.id; +SELECT STRAIGHT_JOIN * FROM a, b WHERE a.id = b.id; +SELECT SQL_CALC_FOUND_ROWS * FROM posts LIMIT 10; +SELECT FOUND_ROWS(); +SELECT * FROM users INTO OUTFILE '/tmp/users.csv'; diff --git a/testdata/realworld/mysql/03_ddl_migrations.sql b/testdata/realworld/mysql/03_ddl_migrations.sql new file mode 100644 index 00000000..3e12486e --- /dev/null +++ b/testdata/realworld/mysql/03_ddl_migrations.sql @@ -0,0 +1,45 @@ +-- DDL and schema migrations (InnoDB, utf8mb4, Laravel/Flyway style) +CREATE TABLE `users` (`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `name` VARCHAR(255) NOT NULL, `email` VARCHAR(255) NOT NULL, `created_at` TIMESTAMP NULL DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `users_email_unique` (`email`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +CREATE TABLE IF NOT EXISTS orders (id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, customer_id INT NOT NULL, total DECIMAL(10,2) NOT NULL DEFAULT 0.00, status ENUM('new','paid','shipped','cancelled') NOT NULL DEFAULT 'new', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_customer (customer_id), CONSTRAINT fk_orders_customer FOREIGN KEY (customer_id) REFERENCES customers (id) ON DELETE CASCADE ON UPDATE RESTRICT) ENGINE=InnoDB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8mb4; +CREATE TABLE user_prefs (user_id INT NOT NULL, flags SET('a','b','c') NOT NULL DEFAULT 'a', PRIMARY KEY (user_id)) ENGINE=InnoDB; +CREATE TABLE t_gen (a INT, b INT, c INT GENERATED ALWAYS AS (a + b) VIRTUAL, d INT AS (a * b) STORED); +CREATE TABLE docs (id INT PRIMARY KEY, body TEXT, FULLTEXT KEY ft_body (body)) ENGINE=InnoDB; +CREATE TABLE events (id BIGINT AUTO_INCREMENT, payload JSON NOT NULL, ts DATETIME(6) NOT NULL, PRIMARY KEY (id)) ENGINE=InnoDB; +CREATE TABLE copy_of_users LIKE users; +CREATE TABLE top_orders AS SELECT * FROM orders WHERE total > 1000; +CREATE TEMPORARY TABLE tmp_ids (id INT PRIMARY KEY); +CREATE TABLE ints (a TINYINT UNSIGNED, b SMALLINT, c MEDIUMINT, d BIGINT UNSIGNED ZEROFILL, e FLOAT(7,3), f DOUBLE, g BIT(8), h YEAR, i TIME, j BLOB, k LONGTEXT, l VARBINARY(16)); +CREATE TABLE logs (id INT, msg VARCHAR(100)) PARTITION BY HASH(id) PARTITIONS 4; +ALTER TABLE users ADD COLUMN phone VARCHAR(20) NULL AFTER email; +ALTER TABLE users ADD COLUMN `locale` CHAR(5) NOT NULL DEFAULT 'en' FIRST; +ALTER TABLE users DROP COLUMN phone; +ALTER TABLE users MODIFY COLUMN name VARCHAR(500) NOT NULL; +ALTER TABLE users CHANGE COLUMN name full_name VARCHAR(255) NOT NULL; +ALTER TABLE users ADD INDEX idx_name (name); +ALTER TABLE users ADD UNIQUE INDEX uq_email (email); +ALTER TABLE users DROP INDEX idx_name; +ALTER TABLE orders ADD CONSTRAINT fk_cust FOREIGN KEY (customer_id) REFERENCES customers (id); +ALTER TABLE orders DROP FOREIGN KEY fk_cust; +ALTER TABLE users ENGINE=InnoDB; +ALTER TABLE users AUTO_INCREMENT = 5000; +ALTER TABLE users ADD COLUMN a INT, ADD COLUMN b INT, DROP COLUMN c; +ALTER TABLE users RENAME TO members; +ALTER TABLE t ALTER COLUMN x SET DEFAULT 5; +ALTER TABLE t ADD FULLTEXT INDEX ft_title (title); +ALTER TABLE t CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci; +CREATE INDEX idx_created ON orders (created_at); +CREATE UNIQUE INDEX uq_sku ON products (sku); +CREATE INDEX idx_prefix ON users (email(10)); +CREATE INDEX idx_desc ON events (ts DESC, id ASC); +CREATE FULLTEXT INDEX ft_all ON docs (title, body); +DROP INDEX idx_created ON orders; +DROP TABLE IF EXISTS tmp_a, tmp_b; +DROP TABLE `old_users`; +TRUNCATE TABLE sessions; +RENAME TABLE old_name TO new_name; +RENAME TABLE a TO b, c TO d; +CREATE VIEW active_users AS SELECT id, name FROM users WHERE deleted_at IS NULL; +CREATE OR REPLACE VIEW v_totals AS SELECT customer_id, SUM(total) AS t FROM orders GROUP BY customer_id; +DROP VIEW IF EXISTS active_users; +CREATE DATABASE IF NOT EXISTS app CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +DROP DATABASE IF EXISTS scratch; diff --git a/testdata/realworld/mysql/04_window_analytics.sql b/testdata/realworld/mysql/04_window_analytics.sql new file mode 100644 index 00000000..4d9ae874 --- /dev/null +++ b/testdata/realworld/mysql/04_window_analytics.sql @@ -0,0 +1,23 @@ +-- Analytics with window functions and CTE chains (MySQL 8.0) +SELECT id, customer_id, total, ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY created_at) AS rn FROM orders; +SELECT id, RANK() OVER (ORDER BY total DESC) AS r, DENSE_RANK() OVER (ORDER BY total DESC) AS dr FROM orders; +SELECT id, SUM(total) OVER (PARTITION BY customer_id ORDER BY created_at ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running FROM orders; +SELECT day, revenue, AVG(revenue) OVER (ORDER BY day ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7 FROM daily_revenue; +SELECT id, LAG(total, 1, 0) OVER (PARTITION BY customer_id ORDER BY created_at) AS prev_total, LEAD(total) OVER (PARTITION BY customer_id ORDER BY created_at) AS next_total FROM orders; +SELECT id, NTILE(4) OVER (ORDER BY total) AS quartile, PERCENT_RANK() OVER (ORDER BY total) AS pr, CUME_DIST() OVER (ORDER BY total) AS cd FROM orders; +SELECT id, FIRST_VALUE(total) OVER w AS f, LAST_VALUE(total) OVER (w ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS l, NTH_VALUE(total, 2) OVER w AS n FROM orders WINDOW w AS (PARTITION BY customer_id ORDER BY created_at); +SELECT id, SUM(total) OVER (ORDER BY created_at RANGE BETWEEN INTERVAL 7 DAY PRECEDING AND CURRENT ROW) FROM orders; +WITH recent AS (SELECT * FROM orders WHERE created_at >= NOW() - INTERVAL 7 DAY) SELECT customer_id, COUNT(*) FROM recent GROUP BY customer_id; +WITH a AS (SELECT 1 AS x), b AS (SELECT x + 1 AS y FROM a), c AS (SELECT y * 2 AS z FROM b) SELECT * FROM c; +WITH RECURSIVE seq (n) AS (SELECT 1 UNION ALL SELECT n + 1 FROM seq WHERE n < 10) SELECT n FROM seq; +WITH RECURSIVE tree AS (SELECT id, parent_id, name, 0 AS depth FROM categories WHERE parent_id IS NULL UNION ALL SELECT c.id, c.parent_id, c.name, t.depth + 1 FROM categories c JOIN tree t ON c.parent_id = t.id) SELECT * FROM tree ORDER BY depth, name; +WITH ranked AS (SELECT p.*, ROW_NUMBER() OVER (PARTITION BY category_id ORDER BY sales DESC) AS rn FROM products p) SELECT * FROM ranked WHERE rn <= 3; +WITH monthly AS (SELECT DATE_FORMAT(created_at, '%Y-%m-01') AS m, SUM(total) AS rev FROM orders GROUP BY 1) SELECT m, rev, rev - LAG(rev) OVER (ORDER BY m) AS delta FROM monthly; +WITH t AS (SELECT * FROM a) UPDATE b JOIN t ON b.id = t.id SET b.v = t.v; +WITH dup AS (SELECT id, ROW_NUMBER() OVER (PARTITION BY email ORDER BY id) AS rn FROM users) DELETE FROM users WHERE id IN (SELECT id FROM dup WHERE rn > 1); +SELECT * FROM (SELECT customer_id, total, ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY total DESC) AS rn FROM orders) x WHERE x.rn = 1; +SELECT customer_id, SUM(total) AS s, SUM(SUM(total)) OVER () AS grand, ROUND(100 * SUM(total) / SUM(SUM(total)) OVER (), 2) AS pct FROM orders GROUP BY customer_id; +SELECT COUNT(*) OVER () AS total_rows, id FROM users LIMIT 10; +SELECT * FROM orders o JOIN LATERAL (SELECT * FROM order_items i WHERE i.order_id = o.id ORDER BY qty DESC LIMIT 1) li ON TRUE; +SELECT a.n FROM (VALUES ROW(1), ROW(2), ROW(3)) AS a (n); +TABLE users ORDER BY id LIMIT 5; diff --git a/testdata/realworld/mysql/05_dialect_specific.sql b/testdata/realworld/mysql/05_dialect_specific.sql new file mode 100644 index 00000000..d49c982c --- /dev/null +++ b/testdata/realworld/mysql/05_dialect_specific.sql @@ -0,0 +1,74 @@ +-- MySQL-specific syntax: JSON, FULLTEXT, index hints, variables, SHOW, etc. +SELECT JSON_EXTRACT(payload, '$.user.id') AS uid FROM events; +SELECT payload->'$.name' AS n, payload->>'$.email' AS e FROM events; +SELECT JSON_UNQUOTE(JSON_EXTRACT(payload, '$.name')) FROM events WHERE JSON_CONTAINS(payload, '"x"', '$.tags'); +SELECT JSON_OBJECT('id', id, 'name', name) FROM users; +SELECT JSON_ARRAYAGG(name) FROM users; +SELECT JSON_OBJECTAGG(k, v) FROM user_settings GROUP BY user_id; +SELECT * FROM events WHERE payload->>'$.type' = 'click' AND payload->'$.n' > 3; +UPDATE events SET payload = JSON_SET(payload, '$.seen', TRUE) WHERE id = 1; +SELECT jt.* FROM events, JSON_TABLE(payload, '$.items[*]' COLUMNS (id INT PATH '$.id', name VARCHAR(50) PATH '$.name')) AS jt; +SELECT * FROM docs WHERE MATCH(title, body) AGAINST ('database' IN NATURAL LANGUAGE MODE); +SELECT *, MATCH(body) AGAINST ('+mysql -oracle' IN BOOLEAN MODE) AS score FROM docs WHERE MATCH(body) AGAINST ('+mysql -oracle' IN BOOLEAN MODE); +SELECT * FROM docs WHERE MATCH(body) AGAINST ('query expansion' WITH QUERY EXPANSION); +SELECT * FROM users USE INDEX (idx_name) WHERE name = 'x'; +SELECT * FROM users FORCE INDEX (PRIMARY) WHERE id > 5; +SELECT * FROM users IGNORE INDEX (idx_a, idx_b) WHERE a = 1; +SELECT * FROM users USE INDEX FOR ORDER BY (idx_created) ORDER BY created_at; +SELECT /*+ MAX_EXECUTION_TIME(1000) */ * FROM big_table; +SELECT /*! SQL_NO_CACHE */ * FROM t; +SELECT @rownum := @rownum + 1 AS rn, t.* FROM t, (SELECT @rownum := 0) r; +SET @a = 1, @b := 2; +SET @total = (SELECT COUNT(*) FROM users); +SET NAMES utf8mb4; +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; +SET SESSION sql_mode = 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION'; +SET GLOBAL max_connections = 500; +SET autocommit = 0; +SET FOREIGN_KEY_CHECKS = 0; +SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +SELECT @@version, @@session.sql_mode, @@global.max_connections; +SHOW TABLES; +SHOW FULL TABLES FROM app LIKE 'user%'; +SHOW COLUMNS FROM users; +SHOW CREATE TABLE users; +SHOW INDEX FROM users; +SHOW DATABASES; +SHOW VARIABLES LIKE 'max_%'; +SHOW STATUS LIKE 'Threads%'; +SHOW PROCESSLIST; +SHOW FULL PROCESSLIST; +SHOW ENGINE INNODB STATUS; +SHOW WARNINGS; +SHOW TABLE STATUS LIKE 'users'; +DESCRIBE users; +DESC users; +EXPLAIN SELECT * FROM users WHERE id = 1; +EXPLAIN FORMAT=JSON SELECT * FROM users; +EXPLAIN ANALYZE SELECT * FROM users; +USE app; +START TRANSACTION; +BEGIN; +COMMIT; +ROLLBACK; +SAVEPOINT sp1; +ROLLBACK TO SAVEPOINT sp1; +RELEASE SAVEPOINT sp1; +LOCK TABLES users WRITE, orders READ; +UNLOCK TABLES; +ANALYZE TABLE users; +OPTIMIZE TABLE users; +CHECK TABLE users; +KILL 123; +FLUSH PRIVILEGES; +LOAD DATA INFILE '/tmp/data.csv' INTO TABLE users FIELDS TERMINATED BY ',' ENCLOSED BY '"' LINES TERMINATED BY '\n' IGNORE 1 LINES (name, email); +SELECT CONVERT(x USING utf8mb4), CAST(y AS CHAR CHARACTER SET utf8mb4), CAST(z AS UNSIGNED), CAST(d AS DECIMAL(10,2)), CAST(s AS JSON) FROM t; +SELECT x COLLATE utf8mb4_bin FROM t WHERE name = _utf8mb4'abc' COLLATE utf8mb4_unicode_ci; +SELECT * FROM t WHERE a <=> NULL AND b REGEXP '^[a-z]+$' AND c RLIKE 'x' AND d DIV 3 = 1 AND e MOD 2 = 0 AND f XOR g; +SELECT 1 FROM DUAL; +SELECT 'it''s', "dq", 0x1F, X'4D', b'101', 1e3, .5; +SELECT BINARY name FROM t; +SELECT INTERVAL(5, 1, 10, 20); +SELECT CONCAT_WS(',', a, b), SUBSTRING_INDEX(email, '@', -1), FIND_IN_SET('a', 'a,b'), UUID(), RAND(), LAST_INSERT_ID(); +SELECT EXTRACT(YEAR FROM created_at), EXTRACT(YEAR_MONTH FROM created_at) FROM t; +SELECT STR_TO_DATE('01/02/2024', '%d/%m/%Y'), FROM_UNIXTIME(1700000000), UNIX_TIMESTAMP('2024-01-01'); diff --git a/testdata/realworld/mysql/06_wordpress.sql b/testdata/realworld/mysql/06_wordpress.sql new file mode 100644 index 00000000..80677c19 --- /dev/null +++ b/testdata/realworld/mysql/06_wordpress.sql @@ -0,0 +1,22 @@ +-- WordPress core / WooCommerce style queries +SELECT option_value FROM wp_options WHERE option_name = 'siteurl' LIMIT 1; +SELECT autoload, option_name, option_value FROM wp_options WHERE autoload = 'yes'; +INSERT INTO wp_options (option_name, option_value, autoload) VALUES ('transient_x', 'v', 'no') ON DUPLICATE KEY UPDATE option_name = VALUES(option_name), option_value = VALUES(option_value), autoload = VALUES(autoload); +SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts WHERE 1=1 AND wp_posts.post_type = 'post' AND ((wp_posts.post_status = 'publish')) ORDER BY wp_posts.post_date DESC LIMIT 0, 10; +SELECT wp_posts.* FROM wp_posts WHERE ID IN (1, 2, 3); +SELECT p.ID FROM wp_posts p INNER JOIN wp_term_relationships tr ON (p.ID = tr.object_id) INNER JOIN wp_term_taxonomy tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id) WHERE tt.taxonomy = 'category' AND tt.term_id IN (4) GROUP BY p.ID ORDER BY p.post_date DESC; +SELECT post_id, meta_key, meta_value FROM wp_postmeta WHERE post_id IN (10, 11, 12) ORDER BY meta_id ASC; +SELECT wp_posts.ID FROM wp_posts INNER JOIN wp_postmeta ON (wp_posts.ID = wp_postmeta.post_id) INNER JOIN wp_postmeta AS mt1 ON (wp_posts.ID = mt1.post_id) WHERE 1=1 AND (wp_postmeta.meta_key = '_price' AND CAST(wp_postmeta.meta_value AS SIGNED) > 10) AND (mt1.meta_key = '_stock' AND mt1.meta_value != '0') GROUP BY wp_posts.ID; +SELECT * FROM wp_users WHERE user_login = 'admin' OR user_email = 'a@b.c' LIMIT 1; +SELECT user_id, meta_key, meta_value FROM wp_usermeta WHERE user_id = 1; +UPDATE wp_posts SET post_content = REPLACE(post_content, 'http://old.com', 'https://new.com'); +UPDATE wp_postmeta SET meta_value = REPLACE(meta_value, 'old', 'new') WHERE meta_key = '_x'; +DELETE FROM wp_options WHERE option_name LIKE '\_transient\_%'; +DELETE a, b FROM wp_posts a LEFT JOIN wp_postmeta b ON b.post_id = a.ID WHERE a.post_type = 'revision'; +SELECT COUNT(*) FROM wp_comments WHERE comment_approved = '1' AND comment_post_ID = 5; +SELECT comment_approved, COUNT(*) AS num_comments FROM wp_comments GROUP BY comment_approved; +SELECT t.*, tt.* FROM wp_terms AS t INNER JOIN wp_term_taxonomy AS tt ON t.term_id = tt.term_id WHERE tt.taxonomy IN ('category') ORDER BY t.name ASC; +SELECT DISTINCT YEAR(post_date) AS year, MONTH(post_date) AS month, count(ID) as posts FROM wp_posts WHERE post_type = 'post' AND post_status = 'publish' GROUP BY YEAR(post_date), MONTH(post_date) ORDER BY post_date DESC; +SELECT order_id, SUM(meta_value) FROM wp_woocommerce_order_itemmeta m INNER JOIN wp_woocommerce_order_items i ON m.order_item_id = i.order_item_id WHERE meta_key = '_line_total' GROUP BY order_id; +SHOW TABLES LIKE 'wp_options'; +CREATE TABLE wp_example (id bigint(20) unsigned NOT NULL AUTO_INCREMENT, name varchar(191) NOT NULL DEFAULT '', data longtext, PRIMARY KEY (id), KEY name (name(191))) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci; diff --git a/testdata/realworld/mysql/07_laravel_magento.sql b/testdata/realworld/mysql/07_laravel_magento.sql new file mode 100644 index 00000000..8ba09075 --- /dev/null +++ b/testdata/realworld/mysql/07_laravel_magento.sql @@ -0,0 +1,17 @@ +-- Laravel Eloquent / queue / Magento EAV style +select * from `jobs` where `queue` = 'default' and ((`reserved_at` is null and `available_at` <= 1700000000) or (`reserved_at` <= 1699999000)) order by `id` asc limit 1 for update; +select `users`.*, (select count(*) from `posts` where `users`.`id` = `posts`.`user_id`) as `posts_count` from `users` where exists (select * from `roles` inner join `role_user` on `roles`.`id` = `role_user`.`role_id` where `users`.`id` = `role_user`.`user_id` and `name` = 'admin'); +select * from `posts` where `posts`.`user_id` in (1, 2, 3) and `posts`.`deleted_at` is null; +select `roles`.*, `role_user`.`user_id` as `pivot_user_id` from `roles` inner join `role_user` on `roles`.`id` = `role_user`.`role_id` where `role_user`.`user_id` in (1, 2); +select * from `users` where date(`created_at`) = '2024-01-01' and year(`created_at`) = 2024 and json_unquote(json_extract(`options`, '$."lang"')) = 'en'; +select max(`batch`) as `aggregate` from `migrations`; +insert into `migrations` (`migration`, `batch`) values ('2024_01_01_000000_create_users_table', 1); +create table `password_resets` (`email` varchar(255) not null, `token` varchar(255) not null, `created_at` timestamp null) default character set utf8mb4 collate 'utf8mb4_unicode_ci'; +alter table `password_resets` add index `password_resets_email_index`(`email`); +update `users` set `remember_token` = 'tok', `updated_at` = '2024-01-01 00:00:00' where `id` = 1; +select * from `cache` where `key` in ('a', 'b'); +select SQL_NO_CACHE e.entity_id, e.sku, v.value AS name FROM catalog_product_entity e INNER JOIN catalog_product_entity_varchar v ON v.entity_id = e.entity_id AND v.store_id = 0 AND v.attribute_id = 73; +SELECT main_table.*, COUNT(items.item_id) AS items_count FROM sales_order AS main_table LEFT JOIN sales_order_item AS items ON items.order_id = main_table.entity_id WHERE (main_table.state IN ('new', 'processing')) GROUP BY main_table.entity_id ORDER BY main_table.created_at DESC LIMIT 20; +SELECT e.*, IF(at_status.value_id > 0, at_status.value, at_status_default.value) AS status FROM catalog_product_entity AS e LEFT JOIN catalog_product_entity_int AS at_status_default ON (at_status_default.entity_id = e.entity_id) AND at_status_default.attribute_id = 97 AND at_status_default.store_id = 0 LEFT JOIN catalog_product_entity_int AS at_status ON (at_status.entity_id = e.entity_id) AND at_status.attribute_id = 97 AND at_status.store_id = 1; +INSERT INTO quote_item (quote_id, product_id, qty) VALUES (1, 2, 3) ON DUPLICATE KEY UPDATE qty = qty + VALUES(qty), updated_at = NOW(); +INSERT INTO new_alias (id, v) VALUES (1, 2) AS new ON DUPLICATE KEY UPDATE v = new.v; diff --git a/testdata/realworld/mysql/08_etl_admin.sql b/testdata/realworld/mysql/08_etl_admin.sql new file mode 100644 index 00000000..27a8844b --- /dev/null +++ b/testdata/realworld/mysql/08_etl_admin.sql @@ -0,0 +1,38 @@ +-- ETL, data maintenance and admin queries +INSERT INTO dim_customer (customer_id, name, region) SELECT c.id, c.name, r.name FROM staging_customers c JOIN regions r ON r.id = c.region_id ON DUPLICATE KEY UPDATE name = VALUES(name), region = VALUES(region); +INSERT INTO daily_stats (day, orders, revenue) SELECT DATE(created_at), COUNT(*), SUM(total) FROM orders WHERE created_at >= CURDATE() - INTERVAL 1 DAY GROUP BY DATE(created_at) ON DUPLICATE KEY UPDATE orders = VALUES(orders), revenue = VALUES(revenue); +UPDATE target t INNER JOIN source s ON s.id = t.id SET t.a = s.a, t.b = s.b WHERE t.a <> s.a OR t.b <> s.b; +UPDATE users u SET u.order_count = (SELECT COUNT(*) FROM orders o WHERE o.customer_id = u.id); +UPDATE orders SET total = total * 1.1 WHERE status = 'new' ORDER BY id LIMIT 1000; +UPDATE t SET a = b, b = a; +UPDATE IGNORE t SET id = id + 1000; +UPDATE LOW_PRIORITY t SET x = 1; +DELETE LOW_PRIORITY QUICK IGNORE FROM logs WHERE ts < NOW() - INTERVAL 90 DAY; +DELETE FROM t1 USING t1 LEFT JOIN t2 ON t1.id = t2.id WHERE t2.id IS NULL; +INSERT LOW_PRIORITY INTO t (a) VALUES (1); +REPLACE INTO t (id, v) SELECT id, v FROM staging; +REPLACE t SET id = 1, v = 'x'; +SELECT table_schema, table_name, ROUND((data_length + index_length) / 1024 / 1024, 2) AS size_mb FROM information_schema.tables WHERE table_schema = DATABASE() ORDER BY size_mb DESC LIMIT 20; +SELECT user, host FROM mysql.user; +SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = 'app' AND TABLE_NAME = 'users' ORDER BY ORDINAL_POSITION; +SELECT * FROM performance_schema.events_statements_summary_by_digest ORDER BY SUM_TIMER_WAIT DESC LIMIT 10; +SELECT * FROM sys.schema_unused_indexes; +SELECT id, user, host, db, command, time, state, info FROM information_schema.processlist WHERE command <> 'Sleep' ORDER BY time DESC; +GRANT SELECT, INSERT, UPDATE ON app.* TO 'app'@'%'; +GRANT ALL PRIVILEGES ON *.* TO 'admin'@'localhost' WITH GRANT OPTION; +REVOKE INSERT ON app.* FROM 'app'@'%'; +CREATE USER 'app'@'%' IDENTIFIED BY 'secret'; +CREATE USER IF NOT EXISTS 'ro'@'10.0.0.%' IDENTIFIED BY 'x'; +DROP USER IF EXISTS 'old'@'%'; +ALTER USER 'app'@'%' IDENTIFIED BY 'new'; +CREATE ROLE 'reporting'; +CALL refresh_stats(2024, 'full'); +CALL sp_noargs(); +PREPARE stmt FROM 'SELECT * FROM users WHERE id = ?'; +EXECUTE stmt USING @id; +DEALLOCATE PREPARE stmt; +SELECT * FROM users WHERE id = ? AND status = ?; +INSERT INTO t (a, b) VALUES (?, ?), (?, ?); +CREATE TRIGGER trg_bi BEFORE INSERT ON users FOR EACH ROW SET NEW.created_at = NOW(); +CREATE EVENT ev_purge ON SCHEDULE EVERY 1 DAY DO DELETE FROM sessions WHERE expires < NOW(); +CREATE FUNCTION add1(x INT) RETURNS INT DETERMINISTIC RETURN x + 1; diff --git a/testdata/realworld/mysql/09_deep_nesting.sql b/testdata/realworld/mysql/09_deep_nesting.sql new file mode 100644 index 00000000..214aaf0d --- /dev/null +++ b/testdata/realworld/mysql/09_deep_nesting.sql @@ -0,0 +1,35 @@ +-- Deeply nested, many-join, long CTE and expression-heavy queries +SELECT * FROM (SELECT * FROM (SELECT * FROM (SELECT * FROM (SELECT id FROM users) a) b) c) d; +SELECT ((((1 + 2) * 3) - 4) / 5) AS v, -(-1), NOT NOT TRUE, !0, ~5, 1 + 2 * 3 - 4 % 3; +SELECT a FROM t WHERE (a = 1 OR b = 2) AND (c = 3 OR (d = 4 AND (e = 5 OR f = 6))); +SELECT a FROM t WHERE a = 1 OR b = 2 AND c = 3; +SELECT a FROM t WHERE NOT a = 1 AND b BETWEEN 1 AND 5 OR c IN (1, 2) AND d IS NOT NULL; +SELECT 1 << 2, 8 >> 1, 5 & 3, 5 | 3, 5 ^ 3, 'a' || 'b', 'a' && 'b'; +SELECT IF(a > 1, IF(b > 2, 'x', 'y'), CASE WHEN c THEN 'z' ELSE NULL END) FROM t; +SELECT CASE status WHEN 'a' THEN 1 WHEN 'b' THEN 2 ELSE 3 END FROM t; +SELECT COALESCE(NULLIF(a, ''), b, c, 'default') FROM t; +SELECT * FROM t WHERE (a, b) IN ((1, 2), (3, 4)); +SELECT * FROM t WHERE (a, b) = (SELECT x, y FROM u LIMIT 1); +SELECT * FROM t WHERE a = ANY (SELECT x FROM u) AND b > ALL (SELECT y FROM v) AND c = SOME (SELECT z FROM w); +SELECT * FROM t WHERE a > (SELECT MAX(x) FROM u WHERE u.k = t.k AND u.d > (SELECT MIN(d) FROM v WHERE v.k = u.k)); +SELECT a.id, b.id, c.id, d.id, e.id, f.id FROM a JOIN b ON b.a_id = a.id JOIN c ON c.b_id = b.id LEFT JOIN d ON d.c_id = c.id LEFT JOIN e ON e.d_id = d.id RIGHT JOIN f ON f.e_id = e.id WHERE a.x = 1; +SELECT * FROM a LEFT OUTER JOIN b ON a.id = b.id RIGHT OUTER JOIN c ON b.id = c.id INNER JOIN d ON c.id = d.id; +WITH c1 AS (SELECT id, v FROM t1), c2 AS (SELECT id, v FROM t2 WHERE id IN (SELECT id FROM c1)), c3 AS (SELECT c1.id, c1.v + c2.v AS s FROM c1 JOIN c2 USING (id)), c4 AS (SELECT id, s, SUM(s) OVER (ORDER BY id) AS run FROM c3), c5 AS (SELECT * FROM c4 WHERE run > 10) SELECT * FROM c5 ORDER BY id LIMIT 10; +WITH cte (a, b) AS (SELECT 1, 2 UNION ALL SELECT 3, 4) SELECT * FROM cte; +SELECT t.* FROM t WHERE t.id IN (SELECT id FROM (SELECT id FROM u ORDER BY id DESC LIMIT 5) AS x); +SELECT COUNT(*) FROM (SELECT DISTINCT a, b FROM t) AS d; +(SELECT 1) UNION (SELECT 2) ORDER BY 1 LIMIT 1; +SELECT a FROM t1 INTERSECT SELECT a FROM t2; +SELECT a FROM t1 EXCEPT SELECT a FROM t2; +SELECT a, b, COUNT(*) FROM t GROUP BY a, b WITH ROLLUP HAVING COUNT(*) > 1 ORDER BY a DESC, b ASC LIMIT 10 OFFSET 5; +SELECT a FROM t ORDER BY FIELD(a, 'x', 'y', 'z'), RAND() LIMIT 3; +SELECT a FROM t GROUP BY a ORDER BY NULL; +SELECT SUM(DISTINCT a), COUNT(DISTINCT a, b), AVG(a), MIN(a), MAX(a), STDDEV(a), VARIANCE(a), BIT_OR(a), GROUP_CONCAT(a SEPARATOR '|') FROM t; +SELECT x FROM t WHERE x REGEXP BINARY 'A' AND y SOUNDS LIKE 'z' AND w LIKE 'a\_%' ESCAPE '\\'; +SELECT a AS 'alias with space', b "other alias" FROM t; +SELECT t.a, t2.* FROM db1.t JOIN db2.t2 ON t.id = t2.id; +SELECT `db`.`t`.`c` FROM `db`.`t`; +SELECT * FROM t PARTITION (p0, p1); +SELECT * FROM t1 JOIN t2 ON t1.a = t2.a AND t1.b <=> t2.b WHERE t1.c LIKE CONCAT('%', t2.d, '%'); +SELECT * FROM t FOR SHARE; +SELECT * FROM t FOR UPDATE NOWAIT; diff --git a/testdata/realworld/mysql/10_misc_functions.sql b/testdata/realworld/mysql/10_misc_functions.sql new file mode 100644 index 00000000..6e940952 --- /dev/null +++ b/testdata/realworld/mysql/10_misc_functions.sql @@ -0,0 +1,31 @@ +-- String, numeric, date and misc function calls plus assorted statements +SELECT UPPER(name), LOWER(name), TRIM(name), LTRIM(name), RTRIM(name), LENGTH(name), CHAR_LENGTH(name), REVERSE(name), REPEAT('ab', 3), LPAD(id, 8, '0'), RPAD(name, 10, '.') FROM t; +SELECT TRIM(BOTH 'x' FROM name), TRIM(LEADING '0' FROM code), TRIM(TRAILING FROM s) FROM t; +SELECT SUBSTRING(name, 2, 3), SUBSTRING(name FROM 2 FOR 3), LEFT(name, 2), RIGHT(name, 2), LOCATE('a', name), INSTR(name, 'a'), REPLACE(name, 'a', 'b') FROM t; +SELECT ABS(x), CEIL(x), FLOOR(x), ROUND(x, 2), TRUNCATE(x, 1), POW(x, 2), SQRT(x), MOD(x, 3), GREATEST(a, b, c), LEAST(a, b, c), SIGN(x) FROM t; +SELECT NOW(), CURDATE(), CURTIME(), CURRENT_TIMESTAMP, CURRENT_DATE, UTC_TIMESTAMP(), SYSDATE(), LOCALTIME; +SELECT CURRENT_TIMESTAMP(6), NOW(3); +SELECT DAYOFWEEK(d), DAYNAME(d), MONTHNAME(d), WEEK(d), WEEKDAY(d), QUARTER(d), LAST_DAY(d), HOUR(t), MINUTE(t), SECOND(t) FROM t; +SELECT DATE_FORMAT(NOW(), '%W, %M %e, %Y %H:%i:%s'), TIME_FORMAT(t, '%h:%i %p'); +SELECT DATE_ADD(d, INTERVAL 1 DAY), DATE_SUB(d, INTERVAL 2 WEEK), d + INTERVAL 3 HOUR, d - INTERVAL '1:30' HOUR_MINUTE, ADDDATE(d, 7), SUBDATE(d, INTERVAL 1 YEAR) FROM t; +SELECT TIMESTAMPADD(MINUTE, 30, ts), TIMESTAMPDIFF(SECOND, a, b), PERIOD_DIFF(202401, 202312), SEC_TO_TIME(3600), TIME_TO_SEC('01:00:00') FROM t; +SELECT MD5(x), SHA1(x), SHA2(x, 256), HEX(x), UNHEX(h), TO_BASE64(x), FROM_BASE64(y), CRC32(x), AES_ENCRYPT(x, 'k'), INET_ATON(ip), INET_NTOA(n) FROM t; +SELECT IFNULL(a, 0), NULLIF(a, 0), ISNULL(a), IF(a, 1, 2), COALESCE(a, b) FROM t; +SELECT DATABASE(), USER(), CURRENT_USER(), VERSION(), CONNECTION_ID(), ROW_COUNT(), SCHEMA(); +SELECT BENCHMARK(1000, MD5('x')), SLEEP(0); +SELECT JSON_LENGTH(j), JSON_TYPE(j), JSON_VALID(j), JSON_KEYS(j), JSON_SEARCH(j, 'one', 'x'), JSON_MERGE_PATCH(a, b), JSON_REMOVE(j, '$.a'), JSON_ARRAY(1, 2, 3), JSON_INSERT(j, '$.b', 1) FROM t; +SELECT j->'$.a[0]', j->>'$.b.c', j -> '$**.id' FROM t; +SELECT ST_AsText(ST_GeomFromText('POINT(1 1)')), ST_Distance_Sphere(p1, p2) FROM places; +SELECT a FROM t WHERE MATCH(a) AGAINST ('x'); +SELECT id FROM t WHERE ts > '2024-01-01' - INTERVAL 1 DAY; +SELECT CAST('2024-01-01' AS DATE), CAST(1 AS SIGNED INTEGER), CAST(x AS CHAR(10)), CAST(x AS DATETIME), CAST(x AS BINARY), CAST(x AS DOUBLE); +SELECT CONVERT(x, SIGNED), CONVERT(x, CHAR); +SELECT DEFAULT(col) FROM t; +SELECT ROW(1, 2) = ROW(1, 2); +SELECT 'a' 'b'; +SELECT 1 AS `select`, 2 AS `from`; +SELECT `order`, `group`, `key` FROM `index`; +SELECT TRUE, FALSE, NULL; +SELECT x IS TRUE, x IS NOT FALSE, x IS UNKNOWN FROM t; +SELECT NOW() - INTERVAL 1 DAY, NOW() + INTERVAL '1' DAY; +SELECT 1; SELECT 2; diff --git a/testdata/realworld/mysql/11_stored_programs.sql b/testdata/realworld/mysql/11_stored_programs.sql new file mode 100644 index 00000000..bcedec46 --- /dev/null +++ b/testdata/realworld/mysql/11_stored_programs.sql @@ -0,0 +1,37 @@ +-- Short stored-program and DML variants without DELIMITER +CREATE TABLE audit_log (id INT AUTO_INCREMENT PRIMARY KEY, tbl VARCHAR(64), action ENUM('I','U','D'), at TIMESTAMP DEFAULT CURRENT_TIMESTAMP); +CREATE TABLE child (id INT PRIMARY KEY, pid INT, CONSTRAINT fk_p FOREIGN KEY (pid) REFERENCES parent(id) ON DELETE SET NULL); +CREATE TABLE ck (a INT, CONSTRAINT chk_a CHECK (a > 0), b INT CHECK (b < 100)); +CREATE TABLE idx_t (a INT, b INT, c VARCHAR(100), INDEX ix_ab (a, b), INDEX ix_c (c(20)), UNIQUE KEY uq (a, c)); +CREATE TABLE def_expr (id INT PRIMARY KEY, uuid CHAR(36) DEFAULT (UUID()), created DATETIME DEFAULT NOW()); +CREATE TABLE inv (id INT, v INT INVISIBLE, KEY ((v + 1))); +CREATE TABLE cmt (id INT COMMENT 'the id', name VARCHAR(10) COMMENT 'nm') COMMENT='a table'; +CREATE TABLE rowfmt (id INT) ENGINE=InnoDB ROW_FORMAT=DYNAMIC KEY_BLOCK_SIZE=8; +CREATE TABLE big (id INT) ENGINE=MyISAM DEFAULT CHARSET=latin1; +CREATE TABLE `select` (`from` INT, `where` INT); +CREATE TABLE ts (a TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6)); +CREATE TABLE sp (id INT, g GEOMETRY NOT NULL SRID 4326, SPATIAL INDEX(g)); +CREATE TABLE ch (a CHAR(3) CHARACTER SET ascii COLLATE ascii_bin, b TEXT CHARSET utf8mb4); +CREATE TABLE ai (id INT UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY (id)) AUTO_INCREMENT=100; +CREATE TABLE decs (a DECIMAL, b DECIMAL(5), c NUMERIC(8,2), d DEC(4,1), e FIXED(3,1), f REAL, g DOUBLE PRECISION, h BOOL, i BOOLEAN, j INTEGER, k INT(11)); +ALTER TABLE t ADD PRIMARY KEY (id); +ALTER TABLE t DROP PRIMARY KEY; +ALTER TABLE t ADD COLUMN c INT NOT NULL DEFAULT 0 COMMENT 'x', ALGORITHM=INPLACE, LOCK=NONE; +ALTER TABLE t ADD PARTITION (PARTITION p3 VALUES LESS THAN (2030)); +ALTER TABLE t RENAME COLUMN a TO b; +ALTER TABLE t RENAME INDEX i1 TO i2; +ALTER TABLE t DROP CHECK chk_a; +ALTER TABLE t ADD CONSTRAINT uq UNIQUE (a, b); +ALTER TABLE t DISABLE KEYS; +ALTER TABLE t COMMENT = 'new comment'; +ALTER TABLE t DEFAULT CHARSET=utf8mb4; +ALTER TABLE t MODIFY a INT NOT NULL AFTER b; +ALTER DATABASE app CHARACTER SET utf8mb4; +DROP INDEX `PRIMARY` ON t; +DROP TRIGGER IF EXISTS trg_bi; +DROP PROCEDURE IF EXISTS sp_x; +DROP FUNCTION IF EXISTS f_x; +DROP EVENT IF EXISTS ev_purge; +CREATE TABLE IF NOT EXISTS x LIKE y; +CREATE SCHEMA IF NOT EXISTS s; +XA START 'a'; diff --git a/testdata/realworld/mysql/12_orm_misc.sql b/testdata/realworld/mysql/12_orm_misc.sql new file mode 100644 index 00000000..1f5be9da --- /dev/null +++ b/testdata/realworld/mysql/12_orm_misc.sql @@ -0,0 +1,27 @@ +-- SQLAlchemy / Django / Hibernate / Rails generated queries +SELECT `auth_user`.`id`, `auth_user`.`username` FROM `auth_user` WHERE `auth_user`.`is_active` = true ORDER BY `auth_user`.`date_joined` DESC LIMIT 21; +SELECT COUNT(*) AS `__count` FROM `django_session` WHERE `django_session`.`expire_date` < '2024-01-01 00:00:00'; +SELECT `django_content_type`.`id` FROM `django_content_type` WHERE (`django_content_type`.`app_label` = 'auth' AND `django_content_type`.`model` = 'user') LIMIT 21; +SELECT this_.id AS id1_0_0_, this_.name AS name2_0_0_ FROM employee this_ WHERE this_.id = ?; +SELECT e.id, e.name FROM employee e WHERE e.dept_id = ? ORDER BY e.name LIMIT ?, ?; +SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1 /* loading for inspect */; +SELECT 1 AS one FROM `users` WHERE `users`.`email` = BINARY 'a@b.c' LIMIT 1; +INSERT INTO `users` (`email`, `created_at`) VALUES ('a@b.c', '2024-01-01 00:00:00.123456'); +UPDATE `users` SET `login_count` = COALESCE(`login_count`, 0) + 1 WHERE `users`.`id` = 1; +SELECT tbl.col FROM tbl -- trailing comment +WHERE tbl.id = 1; +/* leading block comment */ SELECT 1; +SELECT SCHEMA_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME NOT IN ('mysql', 'sys'); +SELECT @@SESSION.transaction_isolation, @@max_allowed_packet, @@lower_case_table_names, @@autocommit; +SELECT VERSION() AS v; +SELECT 1 + 1 AS two; +SELECT CONNECTION_ID(); +SHOW VARIABLES LIKE 'sql_mode'; +SHOW SESSION VARIABLES LIKE 'tx_isolation'; +SHOW COLLATION; +SHOW CHARACTER SET; +SHOW GRANTS FOR 'app'@'%'; +SHOW CREATE VIEW v; +SHOW FULL COLUMNS FROM users; +SHOW MASTER STATUS; +SHOW SLAVE STATUS; diff --git a/testdata/realworld/oracle/01_app_crud.sql b/testdata/realworld/oracle/01_app_crud.sql new file mode 100644 index 00000000..8bb460d4 --- /dev/null +++ b/testdata/realworld/oracle/01_app_crud.sql @@ -0,0 +1,39 @@ +-- ORM-style application CRUD for an order management schema (Oracle 19c) +SELECT * FROM customers WHERE customer_id = :id; +SELECT c.customer_id, c.first_name, c.last_name, c.email FROM customers c WHERE c.status = 'ACTIVE' AND c.created_at > SYSDATE - 30 ORDER BY c.created_at DESC; +SELECT "ID", "NAME", "CreatedAt" FROM "AppUsers" WHERE "ID" = :1; +INSERT INTO customers (customer_id, first_name, last_name, email, created_at) VALUES (customers_seq.NEXTVAL, :first_name, :last_name, :email, SYSDATE); +INSERT INTO orders (order_id, customer_id, order_date, status, total_amount) VALUES (orders_seq.NEXTVAL, :cust_id, SYSTIMESTAMP, 'NEW', 0); +INSERT INTO order_items (item_id, order_id, product_id, quantity, unit_price) VALUES (order_items_seq.NEXTVAL, :order_id, :product_id, :qty, :price); +INSERT INTO audit_log (id, action, performed_by, performed_at) SELECT audit_seq.NEXTVAL, 'PURGE', USER, SYSTIMESTAMP FROM dual; +INSERT INTO customers_archive SELECT * FROM customers WHERE status = 'CLOSED'; +UPDATE customers SET email = :email, updated_at = SYSDATE WHERE customer_id = :id; +UPDATE orders SET status = 'SHIPPED', shipped_at = SYSTIMESTAMP WHERE order_id = :order_id AND status = 'PAID'; +UPDATE order_items oi SET unit_price = (SELECT p.list_price FROM products p WHERE p.product_id = oi.product_id) WHERE EXISTS (SELECT 1 FROM products p WHERE p.product_id = oi.product_id); +UPDATE employees SET salary = salary * 1.05 WHERE department_id = 50 AND ROWNUM <= 100; +DELETE FROM sessions WHERE expires_at < SYSTIMESTAMP; +DELETE FROM order_items WHERE order_id IN (SELECT order_id FROM orders WHERE status = 'CANCELLED'); +DELETE FROM temp_staging; +SELECT order_id, status FROM orders WHERE order_id = :id FOR UPDATE; +SELECT order_id FROM orders WHERE status = 'NEW' ORDER BY order_date FOR UPDATE SKIP LOCKED; +SELECT account_id, balance FROM accounts WHERE account_id = :id FOR UPDATE NOWAIT; +SELECT * FROM products WHERE ROWNUM <= 10; +SELECT * FROM (SELECT p.*, ROWNUM rn FROM (SELECT * FROM products ORDER BY product_name) p WHERE ROWNUM <= 40) WHERE rn > 20; +SELECT * FROM products ORDER BY product_name OFFSET 20 ROWS FETCH NEXT 20 ROWS ONLY; +SELECT * FROM products ORDER BY list_price DESC FETCH FIRST 10 ROWS ONLY; +SELECT * FROM products ORDER BY list_price DESC FETCH FIRST 5 PERCENT ROWS ONLY; +SELECT * FROM products ORDER BY list_price DESC FETCH FIRST 3 ROWS WITH TIES; +INSERT INTO orders (order_id, customer_id, status) VALUES (orders_seq.NEXTVAL, :cid, 'NEW') RETURNING order_id INTO :new_id; +UPDATE accounts SET balance = balance - :amt WHERE account_id = :id RETURNING balance INTO :new_balance; +SELECT orders_seq.CURRVAL FROM dual; +SELECT customer_id, NVL(phone, 'N/A') AS phone, NVL2(fax, 'HAS_FAX', 'NO_FAX') AS fax_flag FROM customers; +SELECT order_id, DECODE(status, 'N', 'New', 'P', 'Paid', 'S', 'Shipped', 'Unknown') AS status_text FROM orders; +SELECT COALESCE(mobile, phone, email) AS contact FROM customers; +SELECT customer_id FROM customers WHERE UPPER(email) LIKE UPPER(:pattern) ESCAPE '\'; +SELECT * FROM customers WHERE REGEXP_LIKE(email, '^[a-z0-9._%-]+@[a-z0-9.-]+\.[a-z]{2,4}$', 'i'); +SELECT * FROM orders WHERE order_date BETWEEN TO_DATE('2024-01-01', 'YYYY-MM-DD') AND TO_DATE('2024-12-31 23:59:59', 'YYYY-MM-DD HH24:MI:SS'); +SELECT * FROM orders WHERE order_date >= DATE '2024-01-01' AND order_date < DATE '2025-01-01'; +SELECT * FROM events WHERE created_at > TIMESTAMP '2024-06-01 00:00:00'; +SELECT TRUNC(SYSDATE) AS today, TRUNC(SYSDATE, 'MM') AS month_start, ADD_MONTHS(SYSDATE, 3) AS plus3, LAST_DAY(SYSDATE) AS eom FROM dual; +SELECT SYSDATE + INTERVAL '7' DAY FROM dual; +SELECT TO_CHAR(order_date, 'YYYY-MM-DD HH24:MI:SS') FROM orders; diff --git a/testdata/realworld/oracle/02_reporting.sql b/testdata/realworld/oracle/02_reporting.sql new file mode 100644 index 00000000..8b8eb868 --- /dev/null +++ b/testdata/realworld/oracle/02_reporting.sql @@ -0,0 +1,38 @@ +-- Dashboards and BI reports: aggregates, rollups, CTE chains +SELECT department_id, COUNT(*) AS headcount, AVG(salary) AS avg_sal, MAX(salary) AS max_sal FROM employees GROUP BY department_id HAVING COUNT(*) > 5 ORDER BY headcount DESC; +SELECT region, product_line, SUM(amount) AS total FROM sales GROUP BY ROLLUP (region, product_line); +SELECT region, product_line, SUM(amount) AS total FROM sales GROUP BY CUBE (region, product_line); +SELECT region, product_line, channel, SUM(amount) AS total, GROUPING(region) AS g_region, GROUPING_ID(region, product_line) AS gid FROM sales GROUP BY GROUPING SETS ((region, product_line), (channel), ()); +SELECT TO_CHAR(order_date, 'YYYY-MM') AS month, COUNT(*) AS orders, SUM(total_amount) AS revenue FROM orders WHERE order_date >= ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -12) GROUP BY TO_CHAR(order_date, 'YYYY-MM') ORDER BY 1; +WITH monthly AS (SELECT TRUNC(order_date, 'MM') AS m, SUM(total_amount) AS rev FROM orders GROUP BY TRUNC(order_date, 'MM')) SELECT m, rev, LAG(rev) OVER (ORDER BY m) AS prev_rev, ROUND((rev - LAG(rev) OVER (ORDER BY m)) / NULLIF(LAG(rev) OVER (ORDER BY m), 0) * 100, 2) AS growth_pct FROM monthly ORDER BY m; +WITH dept_totals AS (SELECT department_id, SUM(salary) AS total_sal FROM employees GROUP BY department_id), avg_total AS (SELECT AVG(total_sal) AS avg_sal FROM dept_totals) SELECT d.department_name, dt.total_sal FROM dept_totals dt JOIN departments d ON d.department_id = dt.department_id CROSS JOIN avg_total a WHERE dt.total_sal > a.avg_sal; +WITH a AS (SELECT 1 AS x FROM dual), b AS (SELECT x + 1 AS x FROM a), c AS (SELECT x + 1 AS x FROM b), d AS (SELECT x + 1 AS x FROM c) SELECT * FROM d; +WITH emp_cte (emp_id, mgr_id, lvl) AS (SELECT employee_id, manager_id, 1 FROM employees WHERE manager_id IS NULL UNION ALL SELECT e.employee_id, e.manager_id, c.lvl + 1 FROM employees e JOIN emp_cte c ON e.manager_id = c.emp_id) SELECT * FROM emp_cte; +WITH nums (n) AS (SELECT 1 FROM dual UNION ALL SELECT n + 1 FROM nums WHERE n < 10) SELECT n FROM nums; +SELECT c.customer_id, c.last_name, COUNT(o.order_id) AS order_count, NVL(SUM(o.total_amount), 0) AS lifetime_value FROM customers c LEFT JOIN orders o ON o.customer_id = c.customer_id GROUP BY c.customer_id, c.last_name HAVING NVL(SUM(o.total_amount), 0) > 1000 ORDER BY lifetime_value DESC FETCH FIRST 50 ROWS ONLY; +SELECT p.product_name, c.category_name, s.supplier_name, SUM(oi.quantity) AS units, SUM(oi.quantity * oi.unit_price) AS revenue FROM order_items oi JOIN orders o ON o.order_id = oi.order_id JOIN products p ON p.product_id = oi.product_id JOIN categories c ON c.category_id = p.category_id JOIN suppliers s ON s.supplier_id = p.supplier_id JOIN customers cu ON cu.customer_id = o.customer_id WHERE o.status <> 'CANCELLED' AND cu.country = 'US' GROUP BY p.product_name, c.category_name, s.supplier_name ORDER BY revenue DESC; +SELECT * FROM customers c WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id AND o.total_amount > 500) AND NOT EXISTS (SELECT 1 FROM returns r WHERE r.customer_id = c.customer_id); +SELECT department_id, (SELECT MAX(salary) FROM employees e2 WHERE e2.department_id = e.department_id) AS max_sal FROM employees e GROUP BY department_id; +SELECT * FROM employees WHERE salary > ALL (SELECT salary FROM employees WHERE department_id = 30); +SELECT * FROM employees WHERE salary > ANY (SELECT salary FROM employees WHERE department_id = 30); +SELECT * FROM employees WHERE (department_id, job_id) IN (SELECT department_id, job_id FROM job_history); +SELECT employee_id FROM employees MINUS SELECT employee_id FROM terminated_employees; +SELECT employee_id FROM employees INTERSECT SELECT employee_id FROM badges; +SELECT city FROM offices UNION SELECT city FROM warehouses UNION ALL SELECT city FROM stores ORDER BY 1; +SELECT d.department_name, e.cnt FROM departments d, (SELECT department_id, COUNT(*) cnt FROM employees GROUP BY department_id) e WHERE d.department_id = e.department_id(+); +SELECT e.last_name, d.department_name, l.city FROM employees e, departments d, locations l WHERE e.department_id = d.department_id(+) AND d.location_id = l.location_id(+) AND e.salary > 5000; +SELECT e.last_name, m.last_name AS manager FROM employees e, employees m WHERE e.manager_id = m.employee_id (+); +SELECT s.*, d.* FROM sales s INNER JOIN dim_date d ON s.date_key = d.date_key LEFT OUTER JOIN dim_store st ON s.store_key = st.store_key FULL OUTER JOIN dim_promo p ON s.promo_key = p.promo_key; +SELECT * FROM employees e JOIN departments d USING (department_id); +SELECT * FROM employees NATURAL JOIN departments; +SELECT d.department_name, e.last_name FROM departments d CROSS APPLY (SELECT * FROM employees e WHERE e.department_id = d.department_id ORDER BY salary DESC FETCH FIRST 3 ROWS ONLY) e; +SELECT d.department_name, e.last_name FROM departments d, LATERAL (SELECT * FROM employees e WHERE e.department_id = d.department_id) e; +SELECT d.department_name, e.last_name FROM departments d OUTER APPLY (SELECT last_name FROM employees e WHERE e.department_id = d.department_id FETCH FIRST 1 ROW ONLY) e; +SELECT CASE WHEN salary < 3000 THEN 'LOW' WHEN salary < 8000 THEN 'MID' ELSE 'HIGH' END AS band, COUNT(*) FROM employees GROUP BY CASE WHEN salary < 3000 THEN 'LOW' WHEN salary < 8000 THEN 'MID' ELSE 'HIGH' END; +SELECT SUM(CASE WHEN status = 'PAID' THEN 1 ELSE 0 END) AS paid, SUM(CASE WHEN status = 'NEW' THEN 1 ELSE 0 END) AS new_cnt FROM orders; +SELECT COUNT(DISTINCT customer_id) FROM orders WHERE order_date > SYSDATE - 90; +SELECT ROUND(AVG(total_amount), 2) AS avg_order, MEDIAN(total_amount) AS med, STDDEV(total_amount) AS sd, VARIANCE(total_amount) AS var FROM orders; +SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary) AS median_sal, PERCENTILE_DISC(0.9) WITHIN GROUP (ORDER BY salary) AS p90 FROM employees; +SELECT department_id, LISTAGG(last_name, ', ') WITHIN GROUP (ORDER BY last_name) AS names FROM employees GROUP BY department_id; +SELECT LISTAGG(DISTINCT city, ';') WITHIN GROUP (ORDER BY city) FROM customers; +SELECT department_id, LISTAGG(last_name, ', ' ON OVERFLOW TRUNCATE '...' WITH COUNT) WITHIN GROUP (ORDER BY last_name) FROM employees GROUP BY department_id; diff --git a/testdata/realworld/oracle/03_ddl_migrations.sql b/testdata/realworld/oracle/03_ddl_migrations.sql new file mode 100644 index 00000000..6a10f082 --- /dev/null +++ b/testdata/realworld/oracle/03_ddl_migrations.sql @@ -0,0 +1,62 @@ +-- DDL migrations: tables, constraints, indexes, partitioning, sequences, views +CREATE TABLE customers (customer_id NUMBER(10) NOT NULL, first_name VARCHAR2(50 CHAR) NOT NULL, last_name VARCHAR2(50 CHAR) NOT NULL, email VARCHAR2(255) UNIQUE, balance NUMBER(12,2) DEFAULT 0 NOT NULL, notes CLOB, avatar BLOB, created_at TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL, CONSTRAINT pk_customers PRIMARY KEY (customer_id)); +CREATE TABLE orders (order_id NUMBER(12) PRIMARY KEY, customer_id NUMBER(10) NOT NULL REFERENCES customers (customer_id), status VARCHAR2(20) DEFAULT 'NEW' CHECK (status IN ('NEW', 'PAID', 'SHIPPED', 'CANCELLED')), total_amount NUMBER(12,2), order_date DATE DEFAULT SYSDATE); +CREATE TABLE order_items (item_id NUMBER PRIMARY KEY, order_id NUMBER NOT NULL, product_id NUMBER NOT NULL, quantity NUMBER(5) NOT NULL, unit_price NUMBER(10,2) NOT NULL, CONSTRAINT fk_oi_order FOREIGN KEY (order_id) REFERENCES orders (order_id) ON DELETE CASCADE, CONSTRAINT chk_qty CHECK (quantity > 0)); +CREATE TABLE ids_demo (id NUMBER GENERATED ALWAYS AS IDENTITY, name VARCHAR2(100)); +CREATE TABLE ids_demo2 (id NUMBER GENERATED BY DEFAULT AS IDENTITY (START WITH 1000 INCREMENT BY 1), name VARCHAR2(100)); +CREATE TABLE line_totals (qty NUMBER, price NUMBER, total NUMBER GENERATED ALWAYS AS (qty * price) VIRTUAL); +CREATE GLOBAL TEMPORARY TABLE temp_staging (id NUMBER, payload VARCHAR2(4000)) ON COMMIT DELETE ROWS; +CREATE TABLE sales_hist (sale_id NUMBER, sale_date DATE NOT NULL, amount NUMBER(12,2)) PARTITION BY RANGE (sale_date) (PARTITION p2023 VALUES LESS THAN (TO_DATE('2024-01-01', 'YYYY-MM-DD')), PARTITION p2024 VALUES LESS THAN (TO_DATE('2025-01-01', 'YYYY-MM-DD')), PARTITION pmax VALUES LESS THAN (MAXVALUE)); +CREATE TABLE sales_region (sale_id NUMBER, region VARCHAR2(10), amount NUMBER) PARTITION BY LIST (region) (PARTITION p_east VALUES ('NY', 'NJ'), PARTITION p_west VALUES ('CA', 'WA'), PARTITION p_other VALUES (DEFAULT)); +CREATE TABLE sales_hash (sale_id NUMBER, amount NUMBER) PARTITION BY HASH (sale_id) PARTITIONS 8; +CREATE TABLE sales_interval (sale_id NUMBER, sale_date DATE) PARTITION BY RANGE (sale_date) INTERVAL (NUMTOYMINTERVAL(1, 'MONTH')) (PARTITION p0 VALUES LESS THAN (TO_DATE('2020-01-01', 'YYYY-MM-DD'))); +CREATE TABLE emp_copy AS SELECT * FROM employees WHERE department_id = 10; +CREATE TABLE emp_empty AS SELECT * FROM employees WHERE 1 = 0; +CREATE TABLE app_config (key VARCHAR2(100) NOT NULL, value VARCHAR2(4000), PRIMARY KEY (key)) TABLESPACE users PCTFREE 10; +CREATE INDEX idx_orders_cust ON orders (customer_id); +CREATE UNIQUE INDEX uq_customers_email ON customers (email); +CREATE INDEX idx_orders_cust_date ON orders (customer_id, order_date DESC); +CREATE INDEX idx_cust_upper_email ON customers (UPPER(email)); +CREATE BITMAP INDEX bix_orders_status ON orders (status); +CREATE INDEX idx_sales_local ON sales_hist (sale_date) LOCAL; +CREATE INDEX idx_orders_online ON orders (order_date) TABLESPACE idx_ts ONLINE; +CREATE SEQUENCE customers_seq START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE; +CREATE SEQUENCE orders_seq START WITH 1000 INCREMENT BY 1 MAXVALUE 9999999999 CACHE 20 NOORDER; +CREATE VIEW v_active_customers AS SELECT customer_id, first_name, last_name FROM customers WHERE status = 'ACTIVE'; +CREATE OR REPLACE VIEW v_order_summary AS SELECT o.order_id, c.last_name, o.total_amount FROM orders o JOIN customers c ON c.customer_id = o.customer_id WITH READ ONLY; +CREATE OR REPLACE FORCE VIEW v_future AS SELECT * FROM not_yet_created; +CREATE MATERIALIZED VIEW mv_sales_by_month BUILD IMMEDIATE REFRESH FAST ON COMMIT AS SELECT TRUNC(sale_date, 'MM') AS m, SUM(amount) AS total FROM sales_hist GROUP BY TRUNC(sale_date, 'MM'); +CREATE SYNONYM emp FOR hr.employees; +CREATE PUBLIC SYNONYM all_emp FOR hr.employees; +ALTER TABLE customers ADD (phone VARCHAR2(30), loyalty_tier VARCHAR2(10) DEFAULT 'BASIC'); +ALTER TABLE customers ADD phone2 VARCHAR2(30); +ALTER TABLE customers MODIFY (email VARCHAR2(320) NOT NULL); +ALTER TABLE customers DROP COLUMN phone2; +ALTER TABLE customers RENAME COLUMN loyalty_tier TO tier; +ALTER TABLE customers RENAME TO clients; +ALTER TABLE orders ADD CONSTRAINT fk_orders_cust FOREIGN KEY (customer_id) REFERENCES customers (customer_id); +ALTER TABLE orders DROP CONSTRAINT fk_orders_cust; +ALTER TABLE orders DISABLE CONSTRAINT chk_status; +ALTER TABLE orders ENABLE NOVALIDATE CONSTRAINT chk_status; +ALTER TABLE sales_hist ADD PARTITION p2025 VALUES LESS THAN (TO_DATE('2026-01-01', 'YYYY-MM-DD')); +ALTER TABLE sales_hist DROP PARTITION p2023; +ALTER TABLE sales_hist TRUNCATE PARTITION p2024; +ALTER TABLE orders MOVE TABLESPACE users2; +ALTER INDEX idx_orders_cust REBUILD; +ALTER INDEX idx_orders_cust RENAME TO idx_orders_customer; +ALTER SEQUENCE orders_seq INCREMENT BY 10; +DROP TABLE temp_staging; +DROP TABLE old_orders CASCADE CONSTRAINTS PURGE; +DROP INDEX idx_orders_cust; +DROP VIEW v_future; +DROP SEQUENCE customers_seq; +DROP MATERIALIZED VIEW mv_sales_by_month; +TRUNCATE TABLE audit_log; +COMMENT ON TABLE customers IS 'Customer master'; +COMMENT ON COLUMN customers.email IS 'Primary contact email'; +GRANT SELECT, INSERT, UPDATE ON customers TO app_user; +GRANT ALL PRIVILEGES ON orders TO app_admin WITH GRANT OPTION; +REVOKE DELETE ON customers FROM app_user; +CREATE USER app_user IDENTIFIED BY "S3cretPass#1" DEFAULT TABLESPACE users QUOTA UNLIMITED ON users; +CREATE ROLE app_role; +GRANT CREATE SESSION, CREATE TABLE TO app_role; diff --git a/testdata/realworld/oracle/04_window_analytics.sql b/testdata/realworld/oracle/04_window_analytics.sql new file mode 100644 index 00000000..cd7d0698 --- /dev/null +++ b/testdata/realworld/oracle/04_window_analytics.sql @@ -0,0 +1,29 @@ +-- Analytic and window functions, including KEEP DENSE_RANK and frames +SELECT employee_id, department_id, salary, RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rnk, DENSE_RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS drnk, ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC, employee_id) AS rn FROM employees; +SELECT * FROM (SELECT e.*, ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rn FROM employees e) WHERE rn <= 3; +SELECT employee_id, salary, SUM(salary) OVER (ORDER BY hire_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_total FROM employees; +SELECT order_date, amount, AVG(amount) OVER (ORDER BY order_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7 FROM daily_sales; +SELECT order_date, amount, SUM(amount) OVER (ORDER BY order_date RANGE BETWEEN INTERVAL '7' DAY PRECEDING AND CURRENT ROW) AS week_sum FROM daily_sales; +SELECT employee_id, salary, LAG(salary, 1, 0) OVER (ORDER BY hire_date) AS prev_sal, LEAD(salary) OVER (ORDER BY hire_date) AS next_sal FROM employees; +SELECT employee_id, FIRST_VALUE(salary) OVER (PARTITION BY department_id ORDER BY hire_date) AS first_sal, LAST_VALUE(salary) OVER (PARTITION BY department_id ORDER BY hire_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS last_sal FROM employees; +SELECT employee_id, NTILE(4) OVER (ORDER BY salary) AS quartile, PERCENT_RANK() OVER (ORDER BY salary) AS pr, CUME_DIST() OVER (ORDER BY salary) AS cd FROM employees; +SELECT department_id, MIN(salary) KEEP (DENSE_RANK FIRST ORDER BY hire_date) AS sal_of_earliest, MAX(salary) KEEP (DENSE_RANK LAST ORDER BY hire_date) AS sal_of_latest FROM employees GROUP BY department_id; +SELECT department_id, MAX(last_name) KEEP (DENSE_RANK FIRST ORDER BY salary DESC) AS top_earner FROM employees GROUP BY department_id; +SELECT employee_id, MIN(salary) KEEP (DENSE_RANK FIRST ORDER BY hire_date) OVER (PARTITION BY department_id) AS first_sal FROM employees; +SELECT department_id, SUM(salary) OVER (PARTITION BY department_id) AS dept_total, salary / SUM(salary) OVER (PARTITION BY department_id) AS share FROM employees; +SELECT employee_id, RATIO_TO_REPORT(salary) OVER (PARTITION BY department_id) AS ratio FROM employees; +SELECT employee_id, NTH_VALUE(salary, 2) OVER (ORDER BY salary DESC) AS second_sal FROM employees; +SELECT employee_id, LISTAGG(last_name, ',') WITHIN GROUP (ORDER BY last_name) OVER (PARTITION BY department_id) AS peers FROM employees; +SELECT employee_id, LAG(salary) IGNORE NULLS OVER (ORDER BY hire_date) AS prev_nonnull FROM employees; +SELECT RANK(5000) WITHIN GROUP (ORDER BY salary) AS hyp_rank FROM employees; +SELECT product_id, SUM(amount) OVER (PARTITION BY product_id ORDER BY sale_date ROWS 3 PRECEDING) AS s FROM sales; +SELECT * FROM (SELECT region, product, amount FROM sales) PIVOT (SUM(amount) FOR product IN ('A' AS prod_a, 'B' AS prod_b, 'C' AS prod_c)); +SELECT * FROM (SELECT department_id, job_id, salary FROM employees) PIVOT (SUM(salary) AS total, COUNT(*) AS cnt FOR job_id IN ('IT_PROG' AS it, 'SA_REP' AS sales)); +SELECT * FROM (SELECT year, quarter, revenue FROM fin) PIVOT (SUM(revenue) FOR quarter IN (1 AS q1, 2 AS q2, 3 AS q3, 4 AS q4)) ORDER BY year; +SELECT * FROM quarterly_sales UNPIVOT (amount FOR quarter IN (q1, q2, q3, q4)); +SELECT * FROM quarterly_sales UNPIVOT INCLUDE NULLS (amount FOR quarter IN (q1 AS 'Q1', q2 AS 'Q2')); +SELECT * FROM sales_wide UNPIVOT ((units, revenue) FOR period IN ((u1, r1) AS 'P1', (u2, r2) AS 'P2')); +SELECT * FROM sales PIVOT XML (SUM(amount) FOR product IN (ANY)); +SELECT employee_id, COUNT(*) OVER () AS total_rows FROM employees; +SELECT department_id, employee_id, salary, salary - AVG(salary) OVER (PARTITION BY department_id) AS diff_from_avg FROM employees ORDER BY department_id, diff_from_avg DESC; +SELECT SUM(salary) OVER (ORDER BY salary RANGE UNBOUNDED PRECEDING) FROM employees; diff --git a/testdata/realworld/oracle/05_dialect_specific.sql b/testdata/realworld/oracle/05_dialect_specific.sql new file mode 100644 index 00000000..ba756890 --- /dev/null +++ b/testdata/realworld/oracle/05_dialect_specific.sql @@ -0,0 +1,73 @@ +-- Oracle-specific syntax: hierarchical queries, DUAL, flashback, sequences, special functions +SELECT employee_id, last_name, manager_id, LEVEL FROM employees START WITH manager_id IS NULL CONNECT BY PRIOR employee_id = manager_id; +SELECT LPAD(' ', 2 * (LEVEL - 1)) || last_name AS org_chart FROM employees START WITH manager_id IS NULL CONNECT BY PRIOR employee_id = manager_id ORDER SIBLINGS BY last_name; +SELECT employee_id, SYS_CONNECT_BY_PATH(last_name, '/') AS path FROM employees START WITH employee_id = 100 CONNECT BY PRIOR employee_id = manager_id; +SELECT last_name, CONNECT_BY_ROOT last_name AS root_name, CONNECT_BY_ISLEAF AS is_leaf FROM employees WHERE LEVEL > 1 START WITH manager_id IS NULL CONNECT BY PRIOR employee_id = manager_id; +SELECT employee_id FROM employees CONNECT BY NOCYCLE PRIOR employee_id = manager_id START WITH employee_id = 101; +SELECT LEVEL FROM dual CONNECT BY LEVEL <= 10; +SELECT TRUNC(SYSDATE) - LEVEL + 1 AS dt FROM dual CONNECT BY LEVEL <= 30; +SELECT TO_DATE('2024-01-01', 'YYYY-MM-DD') + LEVEL - 1 AS d FROM dual CONNECT BY LEVEL <= 366; +SELECT REGEXP_SUBSTR(:csv, '[^,]+', 1, LEVEL) AS token FROM dual CONNECT BY REGEXP_SUBSTR(:csv, '[^,]+', 1, LEVEL) IS NOT NULL; +SELECT employee_id, PRIOR last_name AS manager_name FROM employees CONNECT BY PRIOR employee_id = manager_id AND LEVEL <= 3 START WITH manager_id IS NULL; +SELECT SYSDATE FROM dual; +SELECT SYSTIMESTAMP, CURRENT_TIMESTAMP, LOCALTIMESTAMP, CURRENT_DATE FROM dual; +SELECT USER, SYS_CONTEXT('USERENV', 'SESSION_USER') AS su, SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA') AS cs FROM dual; +SELECT 1 FROM dual; +SELECT sys_guid() FROM dual; +SELECT customers_seq.NEXTVAL, customers_seq.CURRVAL FROM dual; +SELECT * FROM employees AS OF TIMESTAMP (SYSTIMESTAMP - INTERVAL '1' HOUR) WHERE employee_id = 100; +SELECT * FROM employees AS OF SCN 1234567 WHERE department_id = 10; +SELECT * FROM employees AS OF TIMESTAMP TO_TIMESTAMP('2024-06-01 10:00:00', 'YYYY-MM-DD HH24:MI:SS'); +SELECT versions_starttime, versions_endtime, versions_operation, salary FROM employees VERSIONS BETWEEN TIMESTAMP SYSTIMESTAMP - INTERVAL '1' DAY AND SYSTIMESTAMP WHERE employee_id = 100; +FLASHBACK TABLE employees TO TIMESTAMP SYSTIMESTAMP - INTERVAL '15' MINUTE; +FLASHBACK TABLE orders TO BEFORE DROP; +SELECT ROWID, employee_id FROM employees WHERE ROWNUM = 1; +SELECT * FROM employees WHERE ROWID = :rid; +SELECT ORA_ROWSCN, employee_id FROM employees WHERE employee_id = 100; +SELECT employee_id, TO_CHAR(hire_date, 'DD-MON-YYYY') AS hired, TO_CHAR(salary, 'FM$999,999.00') AS sal, TO_NUMBER('1,234.50', '9,999.99') AS n FROM employees; +SELECT TO_CHAR(SYSDATE, 'Day, DD Month YYYY', 'NLS_DATE_LANGUAGE=ENGLISH') FROM dual; +SELECT MONTHS_BETWEEN(SYSDATE, hire_date) / 12 AS years, NEXT_DAY(SYSDATE, 'MONDAY') AS next_mon, EXTRACT(YEAR FROM hire_date) AS yr FROM employees; +SELECT CAST(salary AS VARCHAR2(20)) AS s, CAST(hire_date AS TIMESTAMP) AS ts, CAST(SYSTIMESTAMP AS DATE) FROM employees; +SELECT SUBSTR(last_name, 1, 3), INSTR(email, '@'), LENGTH(last_name), REPLACE(phone, '-', ''), TRIM(BOTH ' ' FROM name), LTRIM(name), RTRIM(name, '.'), INITCAP(name), TRANSLATE(name, 'abc', 'xyz') FROM customers; +SELECT NULLIF(a, b), GREATEST(a, b, c), LEAST(a, b, c), SIGN(x), MOD(x, 3), POWER(2, 10), CEIL(1.2), FLOOR(1.8), TRUNC(1.567, 2), ROUND(1.567, 1), ABS(-3) FROM t; +SELECT first_name || ' ' || last_name AS full_name FROM employees; +SELECT q'[It's quoted]' AS s, N'unicode' AS n FROM dual; +SELECT 'O''Brien' FROM dual; +SELECT 1.5E3, .5, 5., 1e-3 FROM dual; +SELECT INTERVAL '1-6' YEAR TO MONTH, INTERVAL '2 03:04:05' DAY TO SECOND FROM dual; +SELECT NUMTODSINTERVAL(5, 'MINUTE'), NUMTOYMINTERVAL(2, 'YEAR') FROM dual; +SELECT CAST(MULTISET(SELECT order_id FROM orders WHERE customer_id = c.customer_id) AS id_list_t) FROM customers c; +SELECT * FROM TABLE(my_pkg.get_rows(:p1)); +SELECT * FROM TABLE(CAST(:coll AS number_tab_t)); +SELECT column_value FROM TABLE(sys.odcinumberlist(1, 2, 3)); +SELECT * FROM JSON_TABLE(:doc, '$.items[*]' COLUMNS (id NUMBER PATH '$.id', name VARCHAR2(100) PATH '$.name')) jt; +SELECT JSON_VALUE(payload, '$.user.name') AS uname, JSON_QUERY(payload, '$.tags') AS tags FROM events WHERE JSON_EXISTS(payload, '$.user'); +SELECT JSON_OBJECT('id' VALUE employee_id, 'name' VALUE last_name) FROM employees; +SELECT JSON_ARRAYAGG(last_name ORDER BY last_name) FROM employees; +SELECT XMLELEMENT("emp", XMLATTRIBUTES(employee_id AS "id"), last_name) FROM employees; +SELECT * FROM XMLTABLE('/rows/row' PASSING XMLTYPE(:x) COLUMNS id NUMBER PATH 'id', nm VARCHAR2(50) PATH 'name'); +SELECT /*+ INDEX(e idx_emp_dept) PARALLEL(4) */ employee_id FROM employees e WHERE department_id = 10; +SELECT /*+ FULL(o) NO_INDEX(o idx_orders_cust) */ COUNT(*) FROM orders o; +INSERT /*+ APPEND */ INTO sales_hist SELECT * FROM sales_staging; +SELECT * FROM employees SAMPLE (10); +SELECT * FROM employees PARTITION (p2024); +SELECT * FROM sales_hist PARTITION FOR (TO_DATE('2024-05-01', 'YYYY-MM-DD')); +SELECT * FROM remote_table@prod_link WHERE id = 1; +SELECT * FROM hr.employees e WHERE e.hire_date > SYSDATE - 7; +SELECT e.* FROM scott.emp e, scott.dept d WHERE e.deptno = d.deptno; +SELECT * FROM v$session WHERE username IS NOT NULL; +SELECT * FROM v$sql WHERE sql_text LIKE '%orders%'; +SELECT table_name, num_rows FROM user_tables ORDER BY num_rows DESC NULLS LAST; +SELECT owner, segment_name, ROUND(SUM(bytes) / 1024 / 1024, 2) AS mb FROM dba_segments GROUP BY owner, segment_name ORDER BY mb DESC FETCH FIRST 20 ROWS ONLY; +SELECT column_name, data_type, data_length, nullable FROM all_tab_columns WHERE table_name = UPPER(:tbl) AND owner = :owner ORDER BY column_id; +SELECT constraint_name, constraint_type, search_condition FROM user_constraints WHERE table_name = 'ORDERS'; +SELECT * FROM employees ORDER BY commission_pct DESC NULLS FIRST, last_name ASC NULLS LAST; +SELECT employee_id FROM employees WHERE commission_pct IS NOT NULL AND department_id NOT IN (10, 20, 30); +SELECT * FROM employees WHERE last_name LIKE 'S%' OR first_name LIKE '_a%'; +SELECT * FROM employees WHERE salary * 12 > 100000 AND NOT (department_id = 10 OR department_id = 20); +SELECT -salary, +salary, salary - -1, 2 * (3 + 4) / 5 FROM employees; +SELECT DISTINCT job_id FROM employees; +SELECT UNIQUE job_id FROM employees; +SELECT ALL job_id FROM employees; +SELECT * FROM employees e1 WHERE salary = (SELECT MAX(salary) FROM employees e2 WHERE e2.department_id = e1.department_id); +SELECT * FROM (SELECT * FROM (SELECT * FROM (SELECT employee_id, salary FROM employees) WHERE salary > 1000) WHERE salary < 20000) WHERE ROWNUM < 5; diff --git a/testdata/realworld/oracle/06_etl_merge.sql b/testdata/realworld/oracle/06_etl_merge.sql new file mode 100644 index 00000000..483d46e8 --- /dev/null +++ b/testdata/realworld/oracle/06_etl_merge.sql @@ -0,0 +1,39 @@ +-- ETL and data-loading: MERGE, multitable insert, bulk loads, upserts +MERGE INTO customers c USING customers_stage s ON (c.customer_id = s.customer_id) WHEN MATCHED THEN UPDATE SET c.email = s.email, c.updated_at = SYSDATE WHEN NOT MATCHED THEN INSERT (customer_id, first_name, last_name, email) VALUES (s.customer_id, s.first_name, s.last_name, s.email); +MERGE INTO inventory i USING (SELECT product_id, SUM(qty) AS qty FROM shipments GROUP BY product_id) s ON (i.product_id = s.product_id) WHEN MATCHED THEN UPDATE SET i.on_hand = i.on_hand + s.qty; +MERGE INTO inventory i USING (SELECT :pid AS product_id, :qty AS qty FROM dual) s ON (i.product_id = s.product_id) WHEN MATCHED THEN UPDATE SET i.on_hand = s.qty WHEN NOT MATCHED THEN INSERT (product_id, on_hand) VALUES (s.product_id, s.qty); +MERGE INTO employees e USING hr_updates u ON (e.employee_id = u.employee_id) WHEN MATCHED THEN UPDATE SET e.salary = u.salary DELETE WHERE (u.status = 'TERMINATED'); +MERGE INTO employees e USING hr_updates u ON (e.employee_id = u.employee_id) WHEN MATCHED THEN UPDATE SET e.salary = u.salary WHERE e.salary <> u.salary WHEN NOT MATCHED THEN INSERT (employee_id, salary) VALUES (u.employee_id, u.salary) WHERE u.status = 'ACTIVE'; +MERGE /*+ PARALLEL(t 4) */ INTO target t USING source s ON (t.id = s.id) WHEN NOT MATCHED THEN INSERT VALUES (s.id, s.val); +INSERT ALL INTO sales_east (sale_id, amount) VALUES (sale_id, amount) INTO sales_west (sale_id, amount) VALUES (sale_id, amount) SELECT sale_id, amount FROM sales_stage; +INSERT ALL WHEN region = 'E' THEN INTO sales_east VALUES (sale_id, amount) WHEN region = 'W' THEN INTO sales_west VALUES (sale_id, amount) ELSE INTO sales_other VALUES (sale_id, amount) SELECT sale_id, region, amount FROM sales_stage; +INSERT FIRST WHEN amount > 10000 THEN INTO big_sales VALUES (sale_id, amount) ELSE INTO small_sales VALUES (sale_id, amount) SELECT sale_id, amount FROM sales_stage; +INSERT INTO t1 (a, b) SELECT a, b FROM t2 WHERE NOT EXISTS (SELECT 1 FROM t1 WHERE t1.a = t2.a); +INSERT INTO dest SELECT * FROM src LOG ERRORS INTO err$_dest ('nightly load') REJECT LIMIT UNLIMITED; +INSERT INTO t (id, name) VALUES (1, 'a'); +INSERT INTO t (id, created) VALUES (:1, DEFAULT); +INSERT INTO emp_summary (department_id, total) SELECT department_id, SUM(salary) FROM employees GROUP BY department_id; +INSERT INTO log_table VALUES (log_seq.NEXTVAL, SYSDATE, 'started'); +UPDATE (SELECT e.salary AS old_sal, n.salary AS new_sal FROM employees e JOIN new_salaries n ON e.employee_id = n.employee_id) SET old_sal = new_sal; +UPDATE employees SET (salary, commission_pct) = (SELECT salary, commission_pct FROM employees WHERE employee_id = 205) WHERE employee_id = 206; +UPDATE orders o SET (o.total_amount, o.item_count) = (SELECT SUM(quantity * unit_price), COUNT(*) FROM order_items i WHERE i.order_id = o.order_id) WHERE o.status = 'NEW'; +UPDATE customers SET last_login = SYSTIMESTAMP, login_count = NVL(login_count, 0) + 1 WHERE customer_id = :id; +UPDATE employees SET job_id = DEFAULT WHERE employee_id = 1; +DELETE FROM employees e WHERE e.rowid > (SELECT MIN(e2.rowid) FROM employees e2 WHERE e2.email = e.email); +DELETE FROM (SELECT * FROM employees WHERE department_id = 10) WHERE salary < 1000; +DELETE employees WHERE employee_id = 999; +DELETE FROM orders WHERE order_date < ADD_MONTHS(SYSDATE, -36) RETURNING COUNT(*) INTO :cnt; +CREATE TABLE stage_load AS SELECT * FROM src_table WHERE load_date >= TRUNC(SYSDATE) - 1; +TRUNCATE TABLE stage_load DROP STORAGE; +SELECT COUNT(*) FROM stage_load s WHERE NOT EXISTS (SELECT NULL FROM dest d WHERE d.id = s.id); +SELECT s.id, d.id FROM src s FULL OUTER JOIN dest d ON s.id = d.id WHERE s.id IS NULL OR d.id IS NULL; +SELECT id, COUNT(*) FROM stage_load GROUP BY id HAVING COUNT(*) > 1; +SELECT * FROM stage_load WHERE ORA_HASH(id) = ORA_HASH(:v); +SELECT STANDARD_HASH(payload, 'SHA256') AS h FROM stage_load; +SELECT DBMS_RANDOM.VALUE(1, 100) FROM dual; +SELECT DBMS_LOB.GETLENGTH(notes) FROM customers WHERE notes IS NOT NULL; +SELECT UTL_RAW.CAST_TO_VARCHAR2(HEXTORAW('48656C6C6F')) FROM dual; +SELECT TO_CLOB('abc') || TO_CLOB('def') FROM dual; +SELECT employee_id, TO_CHAR(hire_date, 'IYYY-IW') AS iso_week FROM employees; +SELECT * FROM src_table@etl_link s WHERE s.updated_at > (SELECT NVL(MAX(updated_at), DATE '1970-01-01') FROM dest_table); +SELECT 'A' AS grp, COUNT(*) AS n FROM t1 UNION ALL SELECT 'B', COUNT(*) FROM t2 UNION ALL SELECT 'C', COUNT(*) FROM t3; diff --git a/testdata/realworld/oracle/07_admin_ops.sql b/testdata/realworld/oracle/07_admin_ops.sql new file mode 100644 index 00000000..5ebddc7b --- /dev/null +++ b/testdata/realworld/oracle/07_admin_ops.sql @@ -0,0 +1,53 @@ +-- DBA and operations: session control, transactions, system views, tuning queries +ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'; +ALTER SESSION SET CURRENT_SCHEMA = hr; +ALTER SESSION SET TIME_ZONE = 'UTC'; +ALTER SESSION SET NLS_LANGUAGE = 'AMERICAN' NLS_TERRITORY = 'AMERICA'; +ALTER SESSION ENABLE PARALLEL DML; +ALTER SYSTEM KILL SESSION '123,4567' IMMEDIATE; +ALTER SYSTEM SET open_cursors = 500 SCOPE = BOTH; +ALTER SYSTEM FLUSH SHARED_POOL; +ALTER USER app_user IDENTIFIED BY "N3wPass#2" ACCOUNT UNLOCK; +ALTER USER app_user QUOTA 500M ON users; +ALTER TABLESPACE users ADD DATAFILE '/u01/oradata/users02.dbf' SIZE 1G AUTOEXTEND ON NEXT 100M MAXSIZE UNLIMITED; +CREATE TABLESPACE app_data DATAFILE '/u01/oradata/app_data01.dbf' SIZE 500M AUTOEXTEND ON; +CREATE PROFILE app_profile LIMIT SESSIONS_PER_USER 10 IDLE_TIME 30; +CREATE DATABASE LINK prod_link CONNECT TO remote_user IDENTIFIED BY "pwd" USING 'PRODDB'; +DROP DATABASE LINK prod_link; +DROP USER app_user CASCADE; +DROP ROLE app_role; +DROP TABLESPACE app_data INCLUDING CONTENTS AND DATAFILES; +GRANT EXECUTE ON dbms_stats TO app_user; +GRANT app_role TO app_user; +GRANT SELECT ANY TABLE TO reporting_user; +REVOKE CREATE SESSION FROM app_user; +COMMIT; +COMMIT WORK; +ROLLBACK; +ROLLBACK TO SAVEPOINT sp1; +SAVEPOINT sp1; +SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; +SET TRANSACTION READ ONLY; +LOCK TABLE orders IN EXCLUSIVE MODE NOWAIT; +LOCK TABLE orders IN ROW SHARE MODE; +ANALYZE TABLE orders COMPUTE STATISTICS; +EXPLAIN PLAN FOR SELECT * FROM orders WHERE customer_id = 5; +EXPLAIN PLAN SET STATEMENT_ID = 'q1' FOR SELECT * FROM orders; +SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY); +SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL, NULL, 'ALLSTATS LAST')); +SELECT s.sid, s.serial#, s.username, s.status, s.machine, s.program, q.sql_text FROM v$session s LEFT JOIN v$sql q ON s.sql_id = q.sql_id WHERE s.type = 'USER' AND s.status = 'ACTIVE'; +SELECT l.session_id, o.object_name, l.locked_mode FROM v$locked_object l JOIN dba_objects o ON o.object_id = l.object_id; +SELECT tablespace_name, ROUND(SUM(bytes) / 1048576) AS mb_free FROM dba_free_space GROUP BY tablespace_name; +SELECT df.tablespace_name, df.total_mb, NVL(fs.free_mb, 0) AS free_mb, ROUND((df.total_mb - NVL(fs.free_mb, 0)) / df.total_mb * 100, 1) AS pct_used FROM (SELECT tablespace_name, SUM(bytes) / 1048576 AS total_mb FROM dba_data_files GROUP BY tablespace_name) df LEFT JOIN (SELECT tablespace_name, SUM(bytes) / 1048576 AS free_mb FROM dba_free_space GROUP BY tablespace_name) fs ON df.tablespace_name = fs.tablespace_name ORDER BY pct_used DESC; +SELECT sql_id, executions, elapsed_time / 1000000 AS elapsed_s, buffer_gets, SUBSTR(sql_text, 1, 100) AS txt FROM v$sqlarea WHERE parsing_schema_name = 'APP' ORDER BY elapsed_time DESC FETCH FIRST 25 ROWS ONLY; +SELECT object_name, object_type, status FROM user_objects WHERE status = 'INVALID'; +SELECT index_name, uniqueness, status FROM user_indexes WHERE table_name = 'ORDERS'; +SELECT * FROM nls_session_parameters WHERE parameter LIKE 'NLS_DATE%'; +SELECT banner FROM v$version; +SELECT name, value FROM v$parameter WHERE name IN ('sga_target', 'pga_aggregate_target'); +SELECT username, account_status, expiry_date FROM dba_users WHERE expiry_date < SYSDATE + 14; +SELECT grantee, privilege FROM dba_sys_privs WHERE grantee = :u; +SELECT job_name, state, last_start_date FROM dba_scheduler_jobs WHERE enabled = 'TRUE'; +SELECT * FROM dba_hist_snapshot WHERE begin_interval_time > SYSTIMESTAMP - INTERVAL '1' DAY; +SELECT inst_id, COUNT(*) FROM gv$session GROUP BY inst_id; +SELECT * FROM all_source WHERE name = 'MY_PKG' AND type = 'PACKAGE BODY' ORDER BY line; diff --git a/testdata/realworld/oracle/08_complex_nested.sql b/testdata/realworld/oracle/08_complex_nested.sql new file mode 100644 index 00000000..3cbb10a3 --- /dev/null +++ b/testdata/realworld/oracle/08_complex_nested.sql @@ -0,0 +1,27 @@ +-- Deeply nested and many-join enterprise queries (ERP / finance / HR) +WITH sales_q AS (SELECT region_id, TRUNC(sale_date, 'Q') AS qtr, SUM(amount) AS amt FROM sales GROUP BY region_id, TRUNC(sale_date, 'Q')), ranked AS (SELECT region_id, qtr, amt, RANK() OVER (PARTITION BY qtr ORDER BY amt DESC) AS rk FROM sales_q), top3 AS (SELECT * FROM ranked WHERE rk <= 3), named AS (SELECT t.qtr, r.region_name, t.amt, t.rk FROM top3 t JOIN regions r ON r.region_id = t.region_id), final AS (SELECT n.*, SUM(n.amt) OVER (PARTITION BY n.qtr) AS qtr_total FROM named n) SELECT qtr, region_name, amt, ROUND(amt / qtr_total * 100, 1) AS pct FROM final ORDER BY qtr, rk; +SELECT * FROM (SELECT a.*, ROWNUM AS rnum FROM (SELECT o.order_id, o.order_date, c.last_name, SUM(oi.quantity * oi.unit_price) AS total FROM orders o JOIN customers c ON c.customer_id = o.customer_id JOIN order_items oi ON oi.order_id = o.order_id WHERE o.order_date >= TRUNC(SYSDATE) - 90 GROUP BY o.order_id, o.order_date, c.last_name ORDER BY total DESC) a WHERE ROWNUM <= :max_row) WHERE rnum >= :min_row; +SELECT e.employee_id, e.last_name, d.department_name, j.job_title, l.city, c.country_name, r.region_name, m.last_name AS mgr, (SELECT COUNT(*) FROM employees x WHERE x.manager_id = e.employee_id) AS reports FROM employees e JOIN departments d ON e.department_id = d.department_id JOIN jobs j ON e.job_id = j.job_id JOIN locations l ON d.location_id = l.location_id JOIN countries c ON l.country_id = c.country_id JOIN regions r ON c.region_id = r.region_id LEFT JOIN employees m ON e.manager_id = m.employee_id WHERE e.salary BETWEEN 3000 AND 12000 AND r.region_name IN ('Europe', 'Americas'); +SELECT department_id, (SELECT AVG(salary) FROM employees WHERE department_id = d.department_id) AS avg_sal, (SELECT COUNT(*) FROM (SELECT DISTINCT job_id FROM employees WHERE department_id = d.department_id)) AS distinct_jobs FROM departments d WHERE d.department_id IN (SELECT department_id FROM employees GROUP BY department_id HAVING AVG(salary) > (SELECT AVG(salary) FROM employees)); +SELECT * FROM employees WHERE salary > (SELECT AVG(salary) FROM employees WHERE department_id IN (SELECT department_id FROM departments WHERE location_id IN (SELECT location_id FROM locations WHERE country_id IN (SELECT country_id FROM countries WHERE region_id = 1)))); +SELECT CASE WHEN x.a > 0 THEN CASE WHEN x.b > 0 THEN 'PP' ELSE 'PN' END ELSE CASE WHEN x.b > 0 THEN 'NP' ELSE 'NN' END END AS quad FROM x; +SELECT a.acct_id, a.balance + NVL((SELECT SUM(t.amount) FROM txns t WHERE t.acct_id = a.acct_id AND t.posted = 'N'), 0) AS available FROM accounts a WHERE a.status = 'OPEN'; +SELECT ((a + b) * (c - d)) / NULLIF((e + (f * (g - (h + i)))), 0) AS ratio FROM calc; +SELECT * FROM t WHERE (a = 1 AND (b = 2 OR (c = 3 AND (d = 4 OR e = 5)))) OR (f IS NULL AND NOT (g > 10 AND h < 20)); +SELECT * FROM t WHERE a = 1 OR b = 2 AND c = 3; +SELECT * FROM t WHERE NOT a = 1 AND b = 2; +SELECT 1 + 2 * 3 - 4 / 2 FROM dual; +SELECT * FROM t WHERE a BETWEEN 1 AND 5 AND b NOT BETWEEN 10 AND 20 AND c NOT LIKE 'x%' AND d NOT IN (1, 2); +SELECT NVL((SELECT MAX(x) FROM t1), (SELECT MIN(x) FROM t2)) FROM dual; +SELECT d.department_name, (SELECT LISTAGG(e.last_name, ', ') WITHIN GROUP (ORDER BY e.last_name) FROM employees e WHERE e.department_id = d.department_id) AS emps FROM departments d; +WITH bom (part_id, parent_id, qty, lvl) AS (SELECT part_id, parent_id, qty, 1 FROM parts WHERE parent_id IS NULL UNION ALL SELECT p.part_id, p.parent_id, p.qty * b.qty, b.lvl + 1 FROM parts p JOIN bom b ON p.parent_id = b.part_id) SEARCH DEPTH FIRST BY part_id SET ord CYCLE part_id SET is_cycle TO 'Y' DEFAULT 'N' SELECT * FROM bom ORDER BY ord; +WITH t1 AS (SELECT /*+ MATERIALIZE */ * FROM big_table WHERE flag = 'Y') SELECT COUNT(*) FROM t1 a JOIN t1 b ON a.id = b.parent_id; +SELECT * FROM orders o WHERE o.total_amount = (SELECT MAX(total_amount) KEEP (DENSE_RANK LAST ORDER BY order_date) FROM orders o2 WHERE o2.customer_id = o.customer_id); +SELECT customer_id, order_id, total_amount, SUM(total_amount) OVER (PARTITION BY customer_id ORDER BY order_date, order_id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cum, ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date DESC) AS recency FROM orders; +SELECT t.* FROM (SELECT DISTINCT customer_id FROM orders) o, TABLE(get_recs(o.customer_id)) t; +SELECT * FROM (SELECT e.*, DENSE_RANK() OVER (ORDER BY salary DESC) AS dr FROM employees e) WHERE dr = 3; +SELECT e.employee_id FROM employees e WHERE e.department_id = (SELECT d.department_id FROM departments d WHERE d.department_name = 'Sales') AND e.hire_date = (SELECT MIN(hire_date) FROM employees WHERE department_id = e.department_id); +SELECT a.id FROM a WHERE a.id IN (SELECT b.id FROM b UNION SELECT c.id FROM c) AND a.id NOT IN (SELECT d.id FROM d WHERE d.id IS NOT NULL); +(SELECT id FROM a) UNION ALL (SELECT id FROM b) ORDER BY 1; +SELECT id FROM (SELECT id FROM a UNION ALL SELECT id FROM b) ORDER BY id; +SELECT * FROM a, b, c, d, e WHERE a.id = b.a_id AND b.id = c.b_id AND c.id = d.c_id AND d.id = e.d_id; diff --git a/testdata/realworld/oracle/09_plsql_blocks.sql b/testdata/realworld/oracle/09_plsql_blocks.sql new file mode 100644 index 00000000..56fe6456 --- /dev/null +++ b/testdata/realworld/oracle/09_plsql_blocks.sql @@ -0,0 +1,116 @@ +-- PL/SQL anonymous blocks, routines, triggers and packages as deployment +-- scripts run them through SQL*Plus / SQLcl: each block's inner ';' belong +-- to it, and a "/" line ends it. +BEGIN + NULL; +END; +/ +BEGIN + DBMS_STATS.GATHER_TABLE_STATS(ownname => 'APP', tabname => 'ORDERS'); +END; +/ +DECLARE + v_count NUMBER := 0; +BEGIN + SELECT COUNT(*) INTO v_count FROM orders WHERE status = 'OPEN'; + IF v_count > 100 THEN + UPDATE orders SET priority = 'HIGH' WHERE status = 'OPEN'; + ELSIF v_count > 10 THEN + UPDATE orders SET priority = 'MEDIUM' WHERE status = 'OPEN'; + ELSE + NULL; + END IF; + COMMIT; +END; +/ +BEGIN + FOR r IN (SELECT order_id FROM orders WHERE shipped_at IS NULL) LOOP + UPDATE order_lines SET state = 'PENDING' WHERE order_id = r.order_id; + END LOOP; + COMMIT; +EXCEPTION + WHEN OTHERS THEN + ROLLBACK; + RAISE; +END; +/ +DECLARE + CURSOR c_customers IS SELECT customer_id, email FROM customers WHERE active = 1; + v_sent PLS_INTEGER := 0; +BEGIN + FOR c IN c_customers LOOP + INSERT INTO mail_queue (customer_id, email) VALUES (c.customer_id, c.email); + v_sent := v_sent + 1; + END LOOP; + DBMS_OUTPUT.PUT_LINE('queued ' || v_sent); +END; +/ +CREATE OR REPLACE PROCEDURE archive_orders (p_before IN DATE) AS +BEGIN + INSERT INTO orders_archive SELECT * FROM orders WHERE order_date < p_before; + DELETE FROM orders WHERE order_date < p_before; + COMMIT; +END archive_orders; +/ +CREATE OR REPLACE FUNCTION order_total (p_order_id IN NUMBER) RETURN NUMBER IS + v_total NUMBER; +BEGIN + SELECT SUM(quantity * unit_price) INTO v_total FROM order_lines WHERE order_id = p_order_id; + RETURN NVL(v_total, 0); +END order_total; +/ +CREATE OR REPLACE TRIGGER orders_bi +BEFORE INSERT ON orders +FOR EACH ROW +BEGIN + IF :NEW.order_id IS NULL THEN + :NEW.order_id := orders_seq.NEXTVAL; + END IF; + :NEW.created_at := SYSTIMESTAMP; +END; +/ +CREATE OR REPLACE TRIGGER orders_audit +AFTER UPDATE OF status ON orders +FOR EACH ROW +DECLARE + v_user VARCHAR2(30) := SYS_CONTEXT('USERENV', 'SESSION_USER'); +BEGIN + INSERT INTO order_audit (order_id, old_status, new_status, changed_by) + VALUES (:OLD.order_id, :OLD.status, :NEW.status, v_user); +END; +/ +CREATE OR REPLACE PACKAGE order_api AS + PROCEDURE ship (p_order_id IN NUMBER); + FUNCTION is_shipped (p_order_id IN NUMBER) RETURN BOOLEAN; +END order_api; +/ +CREATE OR REPLACE PACKAGE BODY order_api AS + PROCEDURE ship (p_order_id IN NUMBER) IS + BEGIN + UPDATE orders SET status = 'SHIPPED', shipped_at = SYSDATE WHERE order_id = p_order_id; + END ship; + FUNCTION is_shipped (p_order_id IN NUMBER) RETURN BOOLEAN IS + v_status orders.status%TYPE; + BEGIN + SELECT status INTO v_status FROM orders WHERE order_id = p_order_id; + RETURN v_status = 'SHIPPED'; + END is_shipped; +END order_api; +/ +BEGIN + EXECUTE IMMEDIATE 'DROP TABLE tmp_orders'; +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE != -942 THEN + RAISE; + END IF; +END; +/ +BEGIN + order_api.ship(42); + CASE WHEN order_api.is_shipped(42) THEN DBMS_OUTPUT.PUT_LINE('ok'); ELSE DBMS_OUTPUT.PUT_LINE('not shipped'); END CASE; +END; +/ +SELECT order_id, status FROM orders WHERE order_id = 42; +UPDATE orders SET priority = 'LOW' WHERE status = 'SHIPPED'; +COMMIT; diff --git a/testdata/realworld/postgresql/01_app_crud.sql b/testdata/realworld/postgresql/01_app_crud.sql new file mode 100644 index 00000000..9654de3e --- /dev/null +++ b/testdata/realworld/postgresql/01_app_crud.sql @@ -0,0 +1,27 @@ +-- Rails/Django style ORM generated CRUD for a users/orders app +SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1; +SELECT "users".* FROM "users" WHERE "users"."email" = $1 AND "users"."deleted_at" IS NULL ORDER BY "users"."id" ASC LIMIT $2; +SELECT "orders"."id", "orders"."total_cents", "orders"."status" FROM "orders" WHERE "orders"."user_id" IN ($1, $2, $3) ORDER BY "orders"."created_at" DESC LIMIT 20 OFFSET 40; +SELECT COUNT(*) FROM "orders" WHERE "orders"."status" = 'pending' AND "orders"."created_at" >= NOW() - INTERVAL '7 days'; +INSERT INTO users (email, name, created_at, updated_at) VALUES ($1, $2, NOW(), NOW()) RETURNING id; +INSERT INTO orders (user_id, total_cents, status, metadata, created_at) VALUES ($1, $2, 'pending', '{"source": "web"}'::jsonb, now()) RETURNING *; +INSERT INTO order_items (order_id, product_id, quantity, unit_price_cents) VALUES (1, 10, 2, 1999), (1, 11, 1, 4999), (1, 12, 5, 99) RETURNING id, order_id; +UPDATE users SET name = $1, updated_at = NOW() WHERE id = $2 RETURNING id, name, updated_at; +UPDATE orders SET status = 'shipped', shipped_at = CURRENT_TIMESTAMP WHERE id = ANY($1::bigint[]) AND status = 'paid'; +UPDATE products SET stock = stock - oi.quantity FROM order_items oi WHERE oi.product_id = products.id AND oi.order_id = $1; +DELETE FROM sessions WHERE expires_at < NOW(); +DELETE FROM order_items USING orders WHERE order_items.order_id = orders.id AND orders.status = 'cancelled' RETURNING order_items.id; +DELETE FROM users WHERE id = $1 RETURNING *; +INSERT INTO users (email, name) VALUES ('a@example.com', 'A') ON CONFLICT (email) DO UPDATE SET name = EXCLUDED.name, updated_at = now() RETURNING id, (xmax = 0) AS inserted; +INSERT INTO page_views (page_id, day, hits) VALUES (1, CURRENT_DATE, 1) ON CONFLICT (page_id, day) DO UPDATE SET hits = page_views.hits + 1; +INSERT INTO tags (name) VALUES ('go') ON CONFLICT DO NOTHING; +INSERT INTO tags (name) VALUES ('sql') ON CONFLICT ON CONSTRAINT tags_name_key DO NOTHING RETURNING id; +INSERT INTO accounts (id, balance) VALUES (1, 100) ON CONFLICT (id) DO UPDATE SET balance = accounts.balance + EXCLUDED.balance WHERE accounts.active; +SELECT u.id, u.email, COUNT(o.id) AS order_count FROM users u LEFT JOIN orders o ON o.user_id = u.id GROUP BY u.id, u.email HAVING COUNT(o.id) > 5 ORDER BY order_count DESC; +SELECT * FROM users WHERE email ILIKE '%@example.com' AND name NOT ILIKE 'test%'; +SELECT * FROM products WHERE id = ANY(ARRAY[1, 2, 3]) AND price BETWEEN 10 AND 100; +SELECT * FROM users WHERE created_at > $1 FOR UPDATE SKIP LOCKED; +SELECT id FROM jobs WHERE state = 'queued' ORDER BY priority DESC, id LIMIT 1 FOR UPDATE SKIP LOCKED; +SELECT EXISTS (SELECT 1 FROM users WHERE email = $1); +SELECT * FROM orders WHERE user_id = (SELECT id FROM users WHERE email = $1); +SELECT "posts"."id" FROM "posts" WHERE "posts"."author_id" = $1 AND "posts"."published_at" IS NOT NULL AND ("posts"."title" LIKE $2 OR "posts"."body" LIKE $2) ORDER BY "posts"."published_at" DESC FETCH FIRST 10 ROWS ONLY; diff --git a/testdata/realworld/postgresql/02_reporting.sql b/testdata/realworld/postgresql/02_reporting.sql new file mode 100644 index 00000000..da8624c8 --- /dev/null +++ b/testdata/realworld/postgresql/02_reporting.sql @@ -0,0 +1,29 @@ +-- Dashboard and reporting queries: aggregates, GROUPING SETS, FILTER, CTE chains +SELECT date_trunc('day', created_at) AS day, COUNT(*) AS orders, SUM(total_cents) / 100.0 AS revenue FROM orders WHERE created_at >= date_trunc('month', now()) - INTERVAL '1 month' GROUP BY 1 ORDER BY 1; +SELECT COUNT(*) FILTER (WHERE status = 'paid') AS paid, COUNT(*) FILTER (WHERE status = 'refunded') AS refunded, AVG(total_cents) FILTER (WHERE status = 'paid') AS avg_paid FROM orders; +SELECT region, product_category, SUM(amount) AS total FROM sales GROUP BY GROUPING SETS ((region, product_category), (region), ()) ORDER BY region, product_category; +SELECT region, product_category, SUM(amount) FROM sales GROUP BY ROLLUP (region, product_category); +SELECT region, channel, SUM(amount), GROUPING(region, channel) AS g FROM sales GROUP BY CUBE (region, channel); +SELECT extract(year FROM created_at) AS yr, extract(month FROM created_at) AS mo, COUNT(DISTINCT user_id) AS mau FROM events GROUP BY 1, 2 ORDER BY 1, 2; +WITH monthly AS (SELECT date_trunc('month', created_at) AS m, SUM(total_cents) AS revenue FROM orders GROUP BY 1), growth AS (SELECT m, revenue, LAG(revenue) OVER (ORDER BY m) AS prev FROM monthly) SELECT m, revenue, ROUND(100.0 * (revenue - prev) / NULLIF(prev, 0), 2) AS pct_growth FROM growth ORDER BY m; +WITH RECURSIVE tree AS (SELECT id, parent_id, name, 1 AS depth, ARRAY[id] AS path FROM categories WHERE parent_id IS NULL UNION ALL SELECT c.id, c.parent_id, c.name, t.depth + 1, t.path || c.id FROM categories c JOIN tree t ON c.parent_id = t.id WHERE NOT c.id = ANY(t.path)) SELECT * FROM tree ORDER BY path; +WITH RECURSIVE nums(n) AS (VALUES (1) UNION ALL SELECT n + 1 FROM nums WHERE n < 10) SELECT n FROM nums; +WITH cohort AS (SELECT user_id, date_trunc('week', MIN(created_at)) AS cohort_week FROM events GROUP BY user_id), activity AS (SELECT e.user_id, date_trunc('week', e.created_at) AS active_week FROM events e GROUP BY 1, 2) SELECT c.cohort_week, (a.active_week - c.cohort_week) / 7 AS week_no, COUNT(DISTINCT a.user_id) AS users FROM cohort c JOIN activity a USING (user_id) GROUP BY 1, 2 ORDER BY 1, 2; +SELECT p.category, p.name, p.price FROM products p WHERE p.price > (SELECT AVG(price) FROM products WHERE category = p.category); +SELECT c.name, (SELECT COUNT(*) FROM orders o WHERE o.customer_id = c.id) AS n_orders, (SELECT MAX(o.created_at) FROM orders o WHERE o.customer_id = c.id) AS last_order FROM customers c; +SELECT * FROM (SELECT department, salary, RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS rnk FROM employees) t WHERE rnk <= 3; +SELECT o.id, c.name, p.name, oi.quantity FROM orders o INNER JOIN customers c ON c.id = o.customer_id INNER JOIN order_items oi ON oi.order_id = o.id INNER JOIN products p ON p.id = oi.product_id LEFT JOIN shipments s ON s.order_id = o.id LEFT JOIN warehouses w ON w.id = s.warehouse_id WHERE w.region = 'EU' OR s.id IS NULL; +SELECT a.id FROM a FULL OUTER JOIN b ON a.id = b.id WHERE a.id IS NULL OR b.id IS NULL; +SELECT * FROM a CROSS JOIN b; +SELECT * FROM a NATURAL JOIN b; +SELECT id FROM t1 UNION SELECT id FROM t2 EXCEPT SELECT id FROM t3; +(SELECT id FROM t1 ORDER BY id LIMIT 5) UNION ALL (SELECT id FROM t2 ORDER BY id DESC LIMIT 5); +SELECT id FROM t1 INTERSECT ALL SELECT id FROM t2; +SELECT CASE WHEN total_cents < 1000 THEN 'small' WHEN total_cents < 10000 THEN 'medium' ELSE 'large' END AS bucket, COUNT(*) FROM orders GROUP BY 1; +SELECT COALESCE(NULLIF(trim(name), ''), 'unknown') AS name, GREATEST(a, b, c) AS mx, LEAST(a, b) AS mn FROM t; +SELECT customer_id, SUM(total_cents) AS spend FROM orders GROUP BY customer_id ORDER BY spend DESC NULLS LAST LIMIT 10; +SELECT percentile_cont(0.5) WITHIN GROUP (ORDER BY response_ms) AS p50, percentile_cont(0.95) WITHIN GROUP (ORDER BY response_ms) AS p95 FROM requests; +SELECT mode() WITHIN GROUP (ORDER BY status_code) FROM requests; +SELECT string_agg(name, ', ' ORDER BY name) AS names, array_agg(DISTINCT tag) AS tags FROM items GROUP BY category; +SELECT bool_and(active), bool_or(active), every(active) FROM users; +SELECT * FROM sales TABLESAMPLE SYSTEM (10); diff --git a/testdata/realworld/postgresql/03_ddl_migrations.sql b/testdata/realworld/postgresql/03_ddl_migrations.sql new file mode 100644 index 00000000..ab4a03c1 --- /dev/null +++ b/testdata/realworld/postgresql/03_ddl_migrations.sql @@ -0,0 +1,67 @@ +-- Schema migrations as generated by Rails, Alembic, Flyway and hand-written +CREATE TABLE users (id BIGSERIAL PRIMARY KEY, email VARCHAR(255) NOT NULL UNIQUE, name TEXT, role TEXT NOT NULL DEFAULT 'member' CHECK (role IN ('member', 'admin')), settings JSONB NOT NULL DEFAULT '{}'::jsonb, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now()); +CREATE TABLE IF NOT EXISTS orders (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, user_id BIGINT NOT NULL REFERENCES users (id) ON DELETE CASCADE, total_cents INTEGER NOT NULL CHECK (total_cents >= 0), status TEXT NOT NULL DEFAULT 'pending', created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP); +CREATE TABLE order_items (order_id BIGINT NOT NULL, line_no INT NOT NULL, product_id BIGINT NOT NULL, quantity INT NOT NULL DEFAULT 1, PRIMARY KEY (order_id, line_no), CONSTRAINT fk_order FOREIGN KEY (order_id) REFERENCES orders (id) ON UPDATE CASCADE ON DELETE RESTRICT, CONSTRAINT qty_positive CHECK (quantity > 0)); +CREATE TABLE products (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), sku text UNIQUE NOT NULL, price numeric(12, 2) NOT NULL, tags text[] DEFAULT '{}', dims int[][], attrs jsonb, search tsvector GENERATED ALWAYS AS (to_tsvector('english', coalesce(sku, ''))) STORED); +CREATE TEMP TABLE tmp_ids (id bigint) ON COMMIT DROP; +CREATE UNLOGGED TABLE staging_events (payload jsonb, received_at timestamptz DEFAULT now()); +CREATE TABLE audit_log (LIKE users INCLUDING ALL); +CREATE TABLE new_orders AS SELECT * FROM orders WHERE created_at > now() - interval '30 days'; +CREATE TABLE measurements (city_id int NOT NULL, logdate date NOT NULL, peaktemp int, unitsales int) PARTITION BY RANGE (logdate); +CREATE TABLE measurements_y2024m01 PARTITION OF measurements FOR VALUES FROM ('2024-01-01') TO ('2024-02-01'); +CREATE TABLE measurements_default PARTITION OF measurements DEFAULT; +CREATE TABLE regions_list (id int, region text) PARTITION BY LIST (region); +CREATE TABLE regions_eu PARTITION OF regions_list FOR VALUES IN ('DE', 'FR', 'ES'); +CREATE TABLE hash_part (id int) PARTITION BY HASH (id); +CREATE TABLE hash_part_0 PARTITION OF hash_part FOR VALUES WITH (MODULUS 4, REMAINDER 0); +CREATE INDEX idx_users_email ON users (email); +CREATE UNIQUE INDEX idx_users_lower_email ON users (lower(email)); +CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_orders_user_created ON orders (user_id, created_at DESC); +CREATE INDEX idx_orders_pending ON orders (created_at) WHERE status = 'pending'; +CREATE INDEX idx_orders_cover ON orders (user_id) INCLUDE (total_cents, status); +CREATE INDEX idx_products_attrs ON products USING gin (attrs); +CREATE INDEX idx_products_attrs_path ON products USING gin (attrs jsonb_path_ops); +CREATE INDEX idx_products_tags ON products USING gin (tags); +CREATE INDEX idx_products_search ON products USING gist (search); +CREATE INDEX idx_events_brin ON events USING brin (created_at) WITH (pages_per_range = 32); +CREATE INDEX idx_nulls ON orders (shipped_at NULLS FIRST); +CREATE INDEX idx_hash ON sessions USING hash (token); +ALTER TABLE users ADD COLUMN last_login_at TIMESTAMPTZ; +ALTER TABLE users ADD COLUMN IF NOT EXISTS phone VARCHAR(32) NOT NULL DEFAULT ''; +ALTER TABLE users DROP COLUMN IF EXISTS phone; +ALTER TABLE users ALTER COLUMN name SET NOT NULL; +ALTER TABLE users ALTER COLUMN name DROP NOT NULL; +ALTER TABLE users ALTER COLUMN role SET DEFAULT 'guest'; +ALTER TABLE users ALTER COLUMN role DROP DEFAULT; +ALTER TABLE orders ALTER COLUMN total_cents TYPE BIGINT USING total_cents::bigint; +ALTER TABLE users RENAME COLUMN name TO full_name; +ALTER TABLE users RENAME TO app_users; +ALTER TABLE orders ADD CONSTRAINT orders_user_fk FOREIGN KEY (user_id) REFERENCES users (id) NOT VALID; +ALTER TABLE orders VALIDATE CONSTRAINT orders_user_fk; +ALTER TABLE orders DROP CONSTRAINT IF EXISTS orders_user_fk; +ALTER TABLE orders ADD CONSTRAINT chk_total CHECK (total_cents >= 0); +ALTER TABLE orders ADD CONSTRAINT uq_orders UNIQUE (user_id, created_at); +ALTER TABLE orders ADD PRIMARY KEY (id); +ALTER TABLE orders ADD COLUMN a int, ADD COLUMN b int; +ALTER TABLE orders ENABLE ROW LEVEL SECURITY; +ALTER TABLE measurements ATTACH PARTITION measurements_y2024m01 FOR VALUES FROM ('2024-01-01') TO ('2024-02-01'); +ALTER TABLE measurements DETACH PARTITION measurements_y2024m01; +ALTER TABLE orders SET (fillfactor = 70); +ALTER TABLE orders OWNER TO app_owner; +ALTER INDEX idx_users_email RENAME TO idx_users_email_v2; +DROP INDEX CONCURRENTLY IF EXISTS idx_users_email_v2; +DROP TABLE IF EXISTS tmp_ids, staging_events CASCADE; +DROP TABLE audit_log; +TRUNCATE TABLE sessions, tokens RESTART IDENTITY CASCADE; +CREATE VIEW active_users AS SELECT id, email FROM users WHERE deleted_at IS NULL; +CREATE OR REPLACE VIEW order_totals AS SELECT user_id, SUM(total_cents) AS total FROM orders GROUP BY user_id; +CREATE MATERIALIZED VIEW daily_revenue AS SELECT date_trunc('day', created_at) AS day, SUM(total_cents) AS revenue FROM orders GROUP BY 1 WITH DATA; +CREATE UNIQUE INDEX idx_daily_revenue_day ON daily_revenue (day); +REFRESH MATERIALIZED VIEW CONCURRENTLY daily_revenue; +CREATE SEQUENCE order_seq START WITH 1000 INCREMENT BY 1 CACHE 10; +CREATE SCHEMA IF NOT EXISTS analytics; +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy'); +CREATE DOMAIN positive_int AS integer CHECK (VALUE > 0); +COMMENT ON TABLE users IS 'Application users'; +COMMENT ON COLUMN users.email IS 'Login email'; diff --git a/testdata/realworld/postgresql/04_window_analytics.sql b/testdata/realworld/postgresql/04_window_analytics.sql new file mode 100644 index 00000000..3691e66c --- /dev/null +++ b/testdata/realworld/postgresql/04_window_analytics.sql @@ -0,0 +1,23 @@ +-- Window function analytics: frames, GROUPS, EXCLUDE, named windows +SELECT id, SUM(amount) OVER (ORDER BY created_at ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running FROM payments; +SELECT id, AVG(amount) OVER (ORDER BY created_at ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7 FROM payments; +SELECT id, SUM(amount) OVER (ORDER BY created_at RANGE BETWEEN INTERVAL '1 day' PRECEDING AND CURRENT ROW) FROM payments; +SELECT id, SUM(amount) OVER (ORDER BY grp GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) FROM t; +SELECT id, SUM(amount) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) FROM t; +SELECT id, SUM(amount) OVER (ORDER BY id RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE TIES) FROM t; +SELECT id, SUM(amount) OVER (ORDER BY id ROWS UNBOUNDED PRECEDING EXCLUDE NO OTHERS) FROM t; +SELECT id, ROW_NUMBER() OVER w AS rn, RANK() OVER w AS rk, DENSE_RANK() OVER w AS drk FROM t WINDOW w AS (PARTITION BY dept ORDER BY salary DESC); +SELECT id, SUM(x) OVER (w ORDER BY id) FROM t WINDOW w AS (PARTITION BY grp); +SELECT id, LAG(price, 1, 0) OVER (PARTITION BY product_id ORDER BY ts) AS prev_price, LEAD(price) OVER (PARTITION BY product_id ORDER BY ts) AS next_price FROM prices; +SELECT id, FIRST_VALUE(price) OVER (PARTITION BY product_id ORDER BY ts) AS first_p, LAST_VALUE(price) OVER (PARTITION BY product_id ORDER BY ts ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS last_p, NTH_VALUE(price, 2) OVER (PARTITION BY product_id ORDER BY ts) AS second_p FROM prices; +SELECT id, NTILE(4) OVER (ORDER BY score) AS quartile, PERCENT_RANK() OVER (ORDER BY score) AS pr, CUME_DIST() OVER (ORDER BY score) AS cd FROM scores; +SELECT id, COUNT(*) FILTER (WHERE status = 'ok') OVER (PARTITION BY host ORDER BY ts ROWS BETWEEN 9 PRECEDING AND CURRENT ROW) FROM checks; +SELECT user_id, ts, ts - LAG(ts) OVER (PARTITION BY user_id ORDER BY ts) AS gap FROM events; +SELECT * FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn FROM sessions) s WHERE rn = 1; +WITH ranked AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY email ORDER BY id) AS rn FROM users) DELETE FROM users WHERE id IN (SELECT id FROM ranked WHERE rn > 1); +SELECT DISTINCT ON (user_id) user_id, id, created_at FROM orders ORDER BY user_id, created_at DESC; +SELECT DISTINCT ON (customer_id, date_trunc('day', created_at)) customer_id, created_at, total_cents FROM orders ORDER BY customer_id, date_trunc('day', created_at), total_cents DESC; +SELECT SUM(x) OVER (PARTITION BY a, b ORDER BY c DESC NULLS LAST) FROM t; +SELECT id, SUM(amount) OVER (ORDER BY id RANGE BETWEEN 5 PRECEDING AND 5 FOLLOWING) FROM t; +SELECT array_agg(x ORDER BY y) OVER (PARTITION BY z) FROM t; +SELECT id, SUM(amount) OVER (PARTITION BY dept) / SUM(amount) OVER () AS share FROM t; diff --git a/testdata/realworld/postgresql/05_dialect_specific.sql b/testdata/realworld/postgresql/05_dialect_specific.sql new file mode 100644 index 00000000..13b8307f --- /dev/null +++ b/testdata/realworld/postgresql/05_dialect_specific.sql @@ -0,0 +1,58 @@ +-- PostgreSQL specific: JSONB, arrays, LATERAL, regex, casts, intervals, generate_series +SELECT data->'user'->>'name' AS name, data #>> '{address,city}' AS city FROM events; +SELECT * FROM events WHERE data @> '{"type": "click"}'::jsonb; +SELECT * FROM events WHERE data ? 'user_id' AND data ?| ARRAY['a', 'b'] AND data ?& ARRAY['c', 'd']; +SELECT * FROM events WHERE (data->>'amount')::numeric > 100; +SELECT * FROM events WHERE data #> '{a,b}' = '1'::jsonb; +SELECT * FROM events WHERE '{"a": 1}'::jsonb <@ data; +SELECT data - 'password' AS safe, data || '{"x": 1}'::jsonb AS merged, data #- '{a,b}' AS removed FROM users; +SELECT jsonb_set(data, '{profile,age}', '31'::jsonb, true) FROM users; +SELECT jsonb_build_object('id', id, 'email', email) FROM users; +SELECT jsonb_agg(jsonb_build_object('sku', sku, 'qty', quantity)) FROM order_items GROUP BY order_id; +SELECT e.id, elem->>'name' FROM events e, jsonb_array_elements(e.data->'items') AS elem; +SELECT * FROM jsonb_each_text('{"a": "1", "b": "2"}'::jsonb); +SELECT * FROM events WHERE data @@ '$.items[*].price > 10'; +SELECT jsonb_path_query(data, '$.items[*] ? (@.price > 10)') FROM events; +SELECT * FROM jsonb_to_recordset('[{"a": 1, "b": "x"}]'::jsonb) AS t(a int, b text); +SELECT ARRAY[1, 2, 3] || 4, ARRAY[1, 2] @> ARRAY[1], ARRAY[1, 2] && ARRAY[2, 3]; +SELECT tags[1], tags[1:2], array_length(tags, 1), cardinality(tags) FROM products; +SELECT * FROM products WHERE 'sale' = ANY(tags) AND NOT ('old' = ALL(tags)); +SELECT unnest(tags) AS tag, COUNT(*) FROM products GROUP BY 1; +SELECT * FROM unnest(ARRAY[1, 2, 3], ARRAY['a', 'b', 'c']) AS t(n, l); +SELECT * FROM unnest(ARRAY[10, 20]) WITH ORDINALITY AS t(val, idx); +SELECT u.id, o.* FROM users u CROSS JOIN LATERAL (SELECT * FROM orders WHERE user_id = u.id ORDER BY created_at DESC LIMIT 3) o; +SELECT u.id, o.total FROM users u LEFT JOIN LATERAL (SELECT SUM(total_cents) AS total FROM orders WHERE user_id = u.id) o ON true; +SELECT * FROM users, LATERAL jsonb_array_elements_text(u_tags) AS tag; +SELECT * FROM generate_series(1, 10) AS g(n); +SELECT d::date FROM generate_series('2024-01-01'::date, '2024-12-31'::date, INTERVAL '1 day') AS d; +SELECT g.day, COALESCE(COUNT(o.id), 0) FROM generate_series(CURRENT_DATE - 6, CURRENT_DATE, '1 day') AS g(day) LEFT JOIN orders o ON o.created_at::date = g.day GROUP BY g.day; +SELECT * FROM users WHERE name ~ '^A.*z$' AND name !~ 'test' AND email ~* '@EXAMPLE\.com$' AND email !~* 'spam'; +SELECT * FROM users WHERE name SIMILAR TO '(a|b)%'; +SELECT regexp_replace(phone, '[^0-9]', '', 'g'), regexp_matches(body, '(\d+)', 'g'), split_part(email, '@', 2) FROM users; +SELECT '2024-01-01'::date + INTERVAL '1 month', now() - interval '90 minutes', INTERVAL '1 year 2 months 3 days', CURRENT_TIMESTAMP AT TIME ZONE 'UTC'; +SELECT created_at AT TIME ZONE 'America/New_York' FROM orders; +SELECT 1::int, '1'::text::int, CAST(price AS numeric(10, 2)), price::float8, now()::date, '{1,2}'::int[], 'abc'::varchar(2); +SELECT CAST('2024-01-01' AS timestamptz), TIMESTAMP '2024-01-01 00:00:00', DATE '2024-01-01', TIME '12:00'; +SELECT EXTRACT(EPOCH FROM now()), date_part('dow', now()), age(now(), created_at), make_interval(days => 3) FROM users; +SELECT $$dollar quoted$$, $tag$ body 'with' quotes $tag$, E'tab\there', 'it''s'; +SELECT u.* FROM users u WHERE u.id IS DISTINCT FROM 5 AND u.name IS NOT DISTINCT FROM NULL; +SELECT * FROM tbl WHERE (a, b) IN ((1, 2), (3, 4)) AND (c, d) > (1, 2); +SELECT ROW(1, 2) = ROW(1, 2); +SELECT * FROM users WHERE tsv @@ to_tsquery('english', 'foo & bar') ORDER BY ts_rank(tsv, to_tsquery('foo & bar')) DESC; +SELECT 'a' || 'b', 5 % 3, 2 ^ 10, |/ 25, @ -5, 7 / 2, 5 & 3, 5 | 3, 5 # 3, ~5, 1 << 3, 16 >> 2; +SELECT a <-> b FROM pts; +SELECT * FROM events WHERE ts <@ tstzrange('2024-01-01', '2024-02-01') AND during && daterange('2024-01-01', '2024-01-10'); +SELECT inet '192.168.0.1' << inet '192.168.0.0/24', '10.0.0.1'::inet, macaddr '08:00:2b:01:02:03'; +SELECT gen_random_uuid(), uuid_generate_v4(), md5('x'), encode(digest('x', 'sha256'), 'hex'); +SELECT current_user, session_user, current_schema(), current_setting('search_path'), pg_typeof(1), version(); +SELECT col COLLATE "C" FROM t ORDER BY name COLLATE "en_US"; +SELECT * FROM ONLY parent_tbl; +SELECT * FROM t WHERE x = ANY (SELECT y FROM u) AND z <> ALL (SELECT w FROM v); +SELECT count(*) OVER () FROM t LIMIT ALL OFFSET 5 ROWS; +SELECT * FROM t ORDER BY id FETCH FIRST 5 ROWS WITH TIES; +SELECT * FROM t OFFSET 10 LIMIT 5; +VALUES (1, 'a'), (2, 'b'); +SELECT * FROM (VALUES (1, 'a'), (2, 'b')) AS v(id, name); +TABLE users; +SELECT 1 WHERE EXISTS (SELECT 1) AND NOT EXISTS (SELECT 2); +SELECT * INTO new_tbl FROM old_tbl WHERE x > 1; diff --git a/testdata/realworld/postgresql/06_cte_dml_etl.sql b/testdata/realworld/postgresql/06_cte_dml_etl.sql new file mode 100644 index 00000000..8d04efd0 --- /dev/null +++ b/testdata/realworld/postgresql/06_cte_dml_etl.sql @@ -0,0 +1,28 @@ +-- ETL and data modification CTEs, MERGE, MATERIALIZED hints +WITH moved AS (DELETE FROM queue WHERE created_at < now() - interval '1 day' RETURNING *) INSERT INTO queue_archive SELECT * FROM moved; +WITH ins AS (INSERT INTO users (email) VALUES ('x@y.z') RETURNING id) INSERT INTO profiles (user_id) SELECT id FROM ins; +WITH upd AS (UPDATE accounts SET balance = balance - 100 WHERE id = 1 RETURNING id) UPDATE accounts SET balance = balance + 100 WHERE id = 2 AND EXISTS (SELECT 1 FROM upd); +WITH a AS MATERIALIZED (SELECT * FROM big_table WHERE x > 1), b AS NOT MATERIALIZED (SELECT * FROM a WHERE y < 5) SELECT * FROM b JOIN a USING (id); +WITH s AS (SELECT id, ROW_NUMBER() OVER () AS rn FROM t) SELECT * FROM s WHERE rn BETWEEN 10 AND 20; +WITH RECURSIVE r AS (SELECT 1 AS n UNION SELECT n + 1 FROM r WHERE n < 5) CYCLE n SET is_cycle USING path SELECT * FROM r; +WITH RECURSIVE r(n) AS (SELECT 1 UNION ALL SELECT n + 1 FROM r WHERE n < 5) SEARCH DEPTH FIRST BY n SET ordercol SELECT * FROM r ORDER BY ordercol; +INSERT INTO target (id, name) SELECT id, name FROM source WHERE NOT EXISTS (SELECT 1 FROM target WHERE target.id = source.id); +INSERT INTO target DEFAULT VALUES; +INSERT INTO target (id, name) OVERRIDING SYSTEM VALUE VALUES (1, 'x'); +INSERT INTO t (a) SELECT x FROM generate_series(1, 1000) AS x ON CONFLICT DO NOTHING; +UPDATE t SET (a, b) = (1, 2) WHERE id = 5; +UPDATE t SET (a, b) = (SELECT x, y FROM u WHERE u.id = t.id) WHERE EXISTS (SELECT 1 FROM u WHERE u.id = t.id); +UPDATE t SET data = jsonb_set(data, '{count}', ((data->>'count')::int + 1)::text::jsonb) WHERE id = 7; +UPDATE t AS x SET val = y.val FROM (SELECT id, val FROM src) AS y WHERE x.id = y.id; +UPDATE t SET tags = array_append(tags, 'new'), n = n + 1, updated_at = DEFAULT WHERE 'old' <> ALL(tags); +UPDATE ONLY t SET x = 1 WHERE CURRENT_DATE > d; +DELETE FROM t WHERE id IN (SELECT id FROM t ORDER BY created_at LIMIT 1000); +DELETE FROM t AS x WHERE x.created_at < now() - interval '1 year' RETURNING x.id AS deleted_id; +MERGE INTO inventory AS i USING shipments AS s ON i.sku = s.sku WHEN MATCHED THEN UPDATE SET qty = i.qty + s.qty WHEN NOT MATCHED THEN INSERT (sku, qty) VALUES (s.sku, s.qty); +MERGE INTO customers c USING staging s ON c.id = s.id WHEN MATCHED AND s.deleted THEN DELETE WHEN MATCHED THEN UPDATE SET name = s.name WHEN NOT MATCHED THEN INSERT VALUES (s.id, s.name); +MERGE INTO t USING s ON t.id = s.id WHEN NOT MATCHED BY SOURCE THEN DELETE; +MERGE INTO t USING s ON t.id = s.id WHEN MATCHED THEN DO NOTHING WHEN NOT MATCHED THEN INSERT (id) VALUES (s.id) RETURNING merge_action(), t.*; +SELECT * FROM t1 WHERE id IN (WITH x AS (SELECT 1 AS id) SELECT id FROM x); +SELECT * FROM (SELECT * FROM (SELECT * FROM (SELECT id FROM t) a) b) c; +WITH a AS (SELECT 1 AS x), b AS (SELECT x + 1 AS x FROM a), c AS (SELECT x + 1 AS x FROM b), d AS (SELECT x + 1 AS x FROM c), e AS (SELECT x FROM d UNION ALL SELECT x FROM c) SELECT e.x, a.x FROM e CROSS JOIN a; +WITH t AS (SELECT * FROM generate_series(1, 5) AS n) SELECT n, (SELECT SUM(n) FROM t t2 WHERE t2.n <= t.n) FROM t; diff --git a/testdata/realworld/postgresql/07_admin_ops.sql b/testdata/realworld/postgresql/07_admin_ops.sql new file mode 100644 index 00000000..659aab43 --- /dev/null +++ b/testdata/realworld/postgresql/07_admin_ops.sql @@ -0,0 +1,46 @@ +-- Admin, catalog, transaction control and session statements +BEGIN; +BEGIN ISOLATION LEVEL SERIALIZABLE; +BEGIN TRANSACTION READ ONLY; +COMMIT; +ROLLBACK; +SAVEPOINT sp1; +ROLLBACK TO SAVEPOINT sp1; +RELEASE SAVEPOINT sp1; +SET search_path TO public, analytics; +SET LOCAL statement_timeout = '5s'; +SET SESSION TIME ZONE 'UTC'; +SET lock_timeout = 0; +RESET ALL; +SHOW server_version; +EXPLAIN SELECT * FROM users WHERE id = 1; +EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) SELECT * FROM orders WHERE user_id = 5; +EXPLAIN ANALYZE UPDATE t SET x = 1; +GRANT SELECT, INSERT ON users TO app_rw; +GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO admin_role; +REVOKE UPDATE ON orders FROM readonly; +CREATE ROLE app_user LOGIN PASSWORD 'secret'; +SELECT pg_size_pretty(pg_total_relation_size('orders')), pg_relation_size('orders'); +SELECT schemaname, relname, n_live_tup, n_dead_tup FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 20; +SELECT pid, usename, state, now() - query_start AS runtime, query FROM pg_stat_activity WHERE state <> 'idle' AND pid <> pg_backend_pid() ORDER BY runtime DESC; +SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'mydb' AND state = 'idle in transaction'; +SELECT c.relname, a.attname, format_type(a.atttypid, a.atttypmod) FROM pg_attribute a JOIN pg_class c ON c.oid = a.attrelid JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = 'public' AND a.attnum > 0 AND NOT a.attisdropped; +SELECT table_name, column_name, data_type, is_nullable FROM information_schema.columns WHERE table_schema = 'public' ORDER BY table_name, ordinal_position; +SELECT indexrelid::regclass, indisunique FROM pg_index WHERE indrelid = 'orders'::regclass; +SELECT nspname || '.' || relname AS name FROM pg_class JOIN pg_namespace ON pg_namespace.oid = relnamespace WHERE relkind = 'r'; +SELECT pg_advisory_lock(12345), pg_try_advisory_xact_lock(1, 2); +SELECT nextval('order_seq'), currval('order_seq'), setval('order_seq', 1000); +SELECT set_config('app.current_user', '42', true); +SELECT pg_notify('channel', 'payload'); +LISTEN channel; +NOTIFY channel, 'hello'; +ANALYZE users; +LOCK TABLE users IN ACCESS EXCLUSIVE MODE; +PREPARE stmt (int) AS SELECT * FROM users WHERE id = $1; +EXECUTE stmt(5); +DEALLOCATE stmt; +DO $$ BEGIN RAISE NOTICE 'hi'; END $$; +CREATE POLICY tenant_isolation ON orders USING (tenant_id = current_setting('app.tenant')::int); +CREATE OR REPLACE FUNCTION touch() RETURNS trigger AS $$ BEGIN NEW.updated_at = now(); RETURN NEW; END; $$ LANGUAGE plpgsql; +CREATE TRIGGER trg_touch BEFORE UPDATE ON users FOR EACH ROW EXECUTE FUNCTION touch(); +CREATE FUNCTION add(a int, b int) RETURNS int LANGUAGE sql IMMUTABLE AS $$ SELECT a + b $$; diff --git a/testdata/realworld/postgresql/08_supabase_rls_misc.sql b/testdata/realworld/postgresql/08_supabase_rls_misc.sql new file mode 100644 index 00000000..1ceb521f --- /dev/null +++ b/testdata/realworld/postgresql/08_supabase_rls_misc.sql @@ -0,0 +1,30 @@ +-- Supabase-flavoured and multi-tenant SaaS queries +SELECT * FROM profiles WHERE id = auth.uid(); +SELECT * FROM public.posts p WHERE p.org_id = (current_setting('request.jwt.claims', true)::json->>'org_id')::uuid; +SELECT p.id, p.title, jsonb_build_object('id', a.id, 'name', a.name) AS author FROM posts p JOIN authors a ON a.id = p.author_id WHERE p.published = true ORDER BY p.created_at DESC LIMIT 20; +SELECT id, title, ts_headline('english', body, q) FROM posts, websearch_to_tsquery('english', 'postgres tips') AS q WHERE fts @@ q ORDER BY ts_rank_cd(fts, q) DESC; +SELECT id, embedding <=> '[0.1, 0.2, 0.3]'::vector AS distance FROM documents ORDER BY embedding <=> '[0.1, 0.2, 0.3]' LIMIT 5; +INSERT INTO storage.objects (bucket_id, name, owner) VALUES ('avatars', 'a.png', auth.uid()) RETURNING *; +UPDATE public.profiles SET metadata = coalesce(metadata, '{}'::jsonb) || jsonb_build_object('seen', now()) WHERE id = auth.uid(); +SELECT t.id, t.name, COALESCE(json_agg(m.*) FILTER (WHERE m.id IS NOT NULL), '[]') AS members FROM teams t LEFT JOIN members m ON m.team_id = t.id GROUP BY t.id; +SELECT tenant_id, count(*) AS n, sum(amount) AS total FROM invoices WHERE issued_at >= date_trunc('quarter', current_date) GROUP BY tenant_id HAVING sum(amount) > 0 ORDER BY total DESC; +SELECT to_char(created_at, 'YYYY-MM-DD HH24:MI:SS'), to_char(total, 'FM999,999.00'), to_date('2024-01-02', 'YYYY-MM-DD'), to_timestamp(1700000000) FROM t; +SELECT lpad(id::text, 8, '0'), upper(name), left(name, 3), right(name, 2), substring(name FROM 2 FOR 3), substring(name, '[a-z]+'), position('a' IN name), overlay(name PLACING 'x' FROM 2 FOR 1), trim(BOTH ' ' FROM name), length(name), char_length(name) FROM t; +SELECT round(avg(x)::numeric, 2), ceil(x), floor(x), abs(x), mod(x, 3), power(x, 2), sqrt(x), trunc(x, 1), sign(x) FROM t; +SELECT CURRENT_DATE, CURRENT_TIME, LOCALTIMESTAMP, clock_timestamp(), statement_timestamp(), transaction_timestamp(); +SELECT id FROM t WHERE name IS NULL OR name = '' OR name IN ('a', 'b') OR id NOT IN (1, 2, 3) OR created_at NOT BETWEEN '2024-01-01' AND '2024-12-31'; +SELECT id, name FROM t WHERE NOT (a AND b) OR c AND NOT d; +SELECT a + b * c - d / e % f FROM t; +SELECT -x, +x, NOT flag, x IS TRUE, y IS NOT FALSE, z IS UNKNOWN FROM t; +SELECT * FROM t WHERE a = 1 AND (b = 2 OR c = 3) AND d LIKE 'x%' ESCAPE '\'; +SELECT t.* FROM t JOIN u ON t.id = u.id AND u.kind = 'x' JOIN v ON v.uid = u.id WHERE t.a > 1; +SELECT t.id FROM t JOIN (SELECT id, MAX(ts) AS mx FROM log GROUP BY id) l ON l.id = t.id AND l.mx > t.updated_at; +SELECT * FROM t1 JOIN t2 USING (id) JOIN t3 USING (id, other); +SELECT COUNT(DISTINCT (a, b)) FROM t; +SELECT x FROM t GROUP BY x HAVING COUNT(*) > 1 AND MAX(y) < 10 ORDER BY x LIMIT 3; +SELECT 1 AS "select", 2 AS "order", "user", "group" FROM "table"; +SELECT s.*, (s.a).field FROM s; +SELECT * FROM t WHERE x::text LIKE 'a%' AND (y)::int > 3 AND z::jsonb->>'k' = 'v'; +SELECT array(SELECT id FROM t), array_to_string(ARRAY[1, 2], ','), string_to_array('a,b', ','), ARRAY(SELECT 1 UNION SELECT 2); +SELECT CASE status WHEN 'a' THEN 1 WHEN 'b' THEN 2 ELSE 0 END FROM t; +SELECT ARRAY[[1, 2], [3, 4]], '{{1,2},{3,4}}'::int[][]; diff --git a/testdata/realworld/snowflake/01_app_crud.sql b/testdata/realworld/snowflake/01_app_crud.sql new file mode 100644 index 00000000..18d32784 --- /dev/null +++ b/testdata/realworld/snowflake/01_app_crud.sql @@ -0,0 +1,23 @@ +-- ORM-generated CRUD for an e-commerce app on Snowflake +SELECT id, email, first_name, last_name, created_at FROM users WHERE id = 42; +SELECT "id", "email", "created_at" FROM "PUBLIC"."USERS" WHERE "email" = 'a@example.com' LIMIT 1; +SELECT * FROM orders WHERE user_id = 42 AND status IN ('pending', 'paid') ORDER BY created_at DESC LIMIT 20 OFFSET 40; +INSERT INTO users (id, email, first_name, last_name, created_at) VALUES (1001, 'jane@example.com', 'Jane', 'Doe', CURRENT_TIMESTAMP()); +INSERT INTO order_items (order_id, product_id, quantity, unit_price) VALUES (1, 10, 2, 19.99), (1, 11, 1, 5.50), (1, 12, 3, 7.25); +INSERT INTO audit_log (event, payload, created_at) SELECT 'login', OBJECT_CONSTRUCT('user', 42, 'ip', '10.0.0.1'), CURRENT_TIMESTAMP(); +UPDATE users SET last_login_at = CURRENT_TIMESTAMP(), login_count = login_count + 1 WHERE id = 42; +UPDATE orders SET status = 'shipped', shipped_at = CURRENT_TIMESTAMP() WHERE id IN (SELECT order_id FROM shipments WHERE carrier = 'UPS'); +UPDATE products SET price = products.price * 1.05 FROM categories c WHERE products.category_id = c.id AND c.name = 'Books'; +DELETE FROM sessions WHERE expires_at < DATEADD(day, -7, CURRENT_TIMESTAMP()); +DELETE FROM cart_items USING carts WHERE cart_items.cart_id = carts.id AND carts.abandoned = TRUE; +SELECT u.id, u.email, COUNT(o.id) AS order_count FROM users u LEFT JOIN orders o ON o.user_id = u.id GROUP BY u.id, u.email HAVING COUNT(o.id) > 5 ORDER BY order_count DESC; +SELECT p.id, p.name, c.name AS category FROM products p INNER JOIN categories c ON c.id = p.category_id WHERE p.active AND p.price BETWEEN 10 AND 100; +SELECT * FROM users WHERE email ILIKE '%@example.com' AND deleted_at IS NULL; +SELECT COUNT(*) FROM orders WHERE created_at >= '2024-01-01'::date AND created_at < '2025-01-01'::date; +SELECT id, COALESCE(nickname, first_name, 'anonymous') AS display_name FROM users; +SELECT id, CASE WHEN total > 1000 THEN 'large' WHEN total > 100 THEN 'medium' ELSE 'small' END AS size_bucket FROM orders; +SELECT DISTINCT country FROM addresses WHERE country IS NOT NULL ORDER BY 1; +SELECT TOP 10 id, email FROM users ORDER BY created_at DESC; +INSERT OVERWRITE INTO daily_snapshot SELECT * FROM staging_snapshot; +TRUNCATE TABLE staging_events; +SELECT * FROM products WHERE NOT (price < 5 OR stock = 0) AND name NOT LIKE 'tmp%'; diff --git a/testdata/realworld/snowflake/02_reporting.sql b/testdata/realworld/snowflake/02_reporting.sql new file mode 100644 index 00000000..0c130e4a --- /dev/null +++ b/testdata/realworld/snowflake/02_reporting.sql @@ -0,0 +1,28 @@ +-- BI dashboards and reports: revenue, cohorts, funnels +SELECT DATE_TRUNC('month', order_date) AS month, SUM(total_amount) AS revenue, COUNT(DISTINCT customer_id) AS customers FROM orders GROUP BY 1 ORDER BY 1; +SELECT region, product_line, SUM(revenue) AS revenue FROM sales GROUP BY ROLLUP (region, product_line); +SELECT region, product_line, SUM(revenue) AS revenue FROM sales GROUP BY CUBE (region, product_line); +SELECT region, product_line, SUM(revenue) FROM sales GROUP BY GROUPING SETS ((region, product_line), (region), ()); +SELECT region, SUM(revenue) AS revenue FROM sales GROUP BY ALL; +SELECT customer_id, SUM(amount) AS total FROM payments GROUP BY customer_id HAVING SUM(amount) > 10000 ORDER BY total DESC LIMIT 100; +WITH monthly AS (SELECT DATE_TRUNC('month', created_at) AS m, SUM(amount) AS amt FROM payments GROUP BY 1) SELECT m, amt, amt - LAG(amt) OVER (ORDER BY m) AS delta FROM monthly ORDER BY m; +WITH first_orders AS (SELECT customer_id, MIN(order_date) AS first_date FROM orders GROUP BY customer_id), cohorts AS (SELECT customer_id, DATE_TRUNC('month', first_date) AS cohort_month FROM first_orders), activity AS (SELECT o.customer_id, c.cohort_month, DATEDIFF('month', c.cohort_month, DATE_TRUNC('month', o.order_date)) AS month_number FROM orders o JOIN cohorts c ON c.customer_id = o.customer_id) SELECT cohort_month, month_number, COUNT(DISTINCT customer_id) AS active_customers FROM activity GROUP BY 1, 2 ORDER BY 1, 2; +SELECT COUNT_IF(status = 'completed') AS completed, COUNT_IF(status = 'failed') AS failed, COUNT(*) AS total FROM jobs; +SELECT user_id, LISTAGG(DISTINCT tag, ', ') WITHIN GROUP (ORDER BY tag) AS tags FROM user_tags GROUP BY user_id; +SELECT ARRAY_AGG(product_id) WITHIN GROUP (ORDER BY product_id) AS products FROM order_items WHERE order_id = 7; +SELECT APPROX_COUNT_DISTINCT(user_id) AS dau FROM events WHERE event_date = CURRENT_DATE(); +SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY latency_ms) AS p50, PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY latency_ms) AS p95 FROM requests; +SELECT MEDIAN(amount) AS median_amount, AVG(amount) AS avg_amount, STDDEV(amount) AS sd FROM payments; +SELECT d.dept_name, e.name, e.salary FROM employees e JOIN departments d ON d.id = e.dept_id WHERE e.salary > (SELECT AVG(salary) FROM employees WHERE dept_id = e.dept_id); +SELECT a.name, b.name FROM a FULL OUTER JOIN b ON a.key = b.key WHERE a.key IS NULL OR b.key IS NULL; +SELECT c.name, o.order_date FROM customers c CROSS JOIN (SELECT DISTINCT order_date FROM orders) o; +SELECT * FROM orders o LEFT JOIN customers c USING (customer_id); +SELECT * FROM t1 NATURAL JOIN t2; +SELECT order_id FROM orders UNION ALL SELECT order_id FROM archived_orders; +SELECT id FROM a INTERSECT SELECT id FROM b; +SELECT id FROM a MINUS SELECT id FROM b; +SELECT id FROM a EXCEPT SELECT id FROM b; +SELECT TO_CHAR(order_date, 'YYYY-MM') AS ym, SUM(total) FROM orders GROUP BY 1; +SELECT DAYNAME(order_date) AS dow, COUNT(*) FROM orders GROUP BY 1 ORDER BY 2 DESC; +SELECT TIMESTAMPDIFF(minute, started_at, ended_at) AS duration_min FROM sessions; +SELECT year, quarter, revenue, SUM(revenue) OVER (PARTITION BY year ORDER BY quarter ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS ytd FROM quarterly; diff --git a/testdata/realworld/snowflake/03_ddl_migrations.sql b/testdata/realworld/snowflake/03_ddl_migrations.sql new file mode 100644 index 00000000..e9d5db27 --- /dev/null +++ b/testdata/realworld/snowflake/03_ddl_migrations.sql @@ -0,0 +1,38 @@ +-- Schema migrations and DDL as run by migration tools +CREATE TABLE users (id NUMBER(38,0) NOT NULL, email VARCHAR(255) NOT NULL, first_name VARCHAR, created_at TIMESTAMP_NTZ DEFAULT CURRENT_TIMESTAMP(), CONSTRAINT pk_users PRIMARY KEY (id), CONSTRAINT uq_users_email UNIQUE (email)); +CREATE TABLE IF NOT EXISTS orders (id NUMBER AUTOINCREMENT START 1 INCREMENT 1, user_id NUMBER NOT NULL, total NUMBER(12,2), status VARCHAR(20) DEFAULT 'pending', placed_at TIMESTAMP_TZ, FOREIGN KEY (user_id) REFERENCES users (id)); +CREATE OR REPLACE TABLE events (event_id VARCHAR, payload VARIANT, tags ARRAY, props OBJECT, event_ts TIMESTAMP_LTZ) CLUSTER BY (event_ts); +CREATE TRANSIENT TABLE staging_events (raw VARIANT); +CREATE TEMPORARY TABLE tmp_ids (id NUMBER); +CREATE TABLE sales_clone CLONE sales; +CREATE OR REPLACE TABLE sales_backup CLONE sales AT (TIMESTAMP => '2024-05-01 00:00:00'::timestamp_ltz); +CREATE TABLE sales_before CLONE sales BEFORE (STATEMENT => '8e5d0ca9-005e-44e6-b858-a8f5b37c5726'); +CREATE TABLE order_summary AS SELECT user_id, COUNT(*) AS n, SUM(total) AS total FROM orders GROUP BY user_id; +CREATE OR REPLACE TABLE dim_date AS SELECT DATEADD(day, SEQ4(), '2020-01-01'::date) AS d FROM TABLE(GENERATOR(ROWCOUNT => 3650)); +CREATE TABLE like_users LIKE users; +CREATE OR REPLACE VIEW v_active_users AS SELECT id, email FROM users WHERE deleted_at IS NULL; +CREATE OR REPLACE SECURE VIEW v_orders_secure AS SELECT id, user_id, total FROM orders; +CREATE MATERIALIZED VIEW mv_daily_sales AS SELECT order_date, SUM(total) AS total FROM orders GROUP BY order_date; +CREATE VIEW v_with_cols (a, b) AS SELECT 1, 2; +ALTER TABLE users ADD COLUMN phone VARCHAR(32); +ALTER TABLE users ADD COLUMN IF NOT EXISTS locale VARCHAR(10) DEFAULT 'en'; +ALTER TABLE users DROP COLUMN phone; +ALTER TABLE users RENAME COLUMN first_name TO given_name; +ALTER TABLE users RENAME TO app_users; +ALTER TABLE orders ALTER COLUMN id SET DEFAULT order_seq.NEXTVAL; +ALTER TABLE orders ALTER COLUMN total SET NOT NULL; +ALTER TABLE orders ALTER COLUMN total DROP NOT NULL; +ALTER TABLE orders ADD CONSTRAINT fk_orders_user FOREIGN KEY (user_id) REFERENCES users (id); +ALTER TABLE events CLUSTER BY (event_ts, event_id); +ALTER TABLE events SET DATA_RETENTION_TIME_IN_DAYS = 7; +ALTER TABLE sales SWAP WITH sales_clone; +DROP TABLE IF EXISTS tmp_ids; +DROP VIEW IF EXISTS v_active_users; +DROP TABLE users CASCADE; +UNDROP TABLE users; +CREATE SCHEMA IF NOT EXISTS analytics; +CREATE DATABASE dev_db CLONE prod_db; +CREATE OR REPLACE SEQUENCE order_seq START = 1 INCREMENT = 1; +CREATE FILE FORMAT my_csv TYPE = 'CSV' FIELD_DELIMITER = ',' SKIP_HEADER = 1; +CREATE STAGE my_stage URL = 's3://bucket/path/' FILE_FORMAT = (TYPE = 'PARQUET'); +COMMENT ON TABLE users IS 'Application users'; diff --git a/testdata/realworld/snowflake/04_window_analytics.sql b/testdata/realworld/snowflake/04_window_analytics.sql new file mode 100644 index 00000000..308c1a9a --- /dev/null +++ b/testdata/realworld/snowflake/04_window_analytics.sql @@ -0,0 +1,19 @@ +-- Window function analytics, QUALIFY, and ranking +SELECT id, customer_id, total, ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY total DESC) AS rn FROM orders; +SELECT * FROM orders QUALIFY ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date DESC) = 1; +SELECT customer_id, order_date, total FROM orders QUALIFY RANK() OVER (PARTITION BY customer_id ORDER BY total DESC) <= 3; +SELECT * FROM events QUALIFY ROW_NUMBER() OVER (PARTITION BY event_id ORDER BY loaded_at DESC) = 1 ORDER BY event_id; +SELECT a, b, SUM(c) AS s FROM t GROUP BY a, b QUALIFY DENSE_RANK() OVER (PARTITION BY a ORDER BY SUM(c) DESC) = 1; +SELECT day, revenue, AVG(revenue) OVER (ORDER BY day ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7 FROM daily_revenue; +SELECT day, revenue, LAG(revenue, 7) OVER (ORDER BY day) AS last_week, LEAD(revenue) OVER (ORDER BY day) AS next_day FROM daily_revenue; +SELECT id, FIRST_VALUE(price) OVER (PARTITION BY sku ORDER BY ts) AS first_price, LAST_VALUE(price) OVER (PARTITION BY sku ORDER BY ts ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS last_price FROM prices; +SELECT id, NTILE(4) OVER (ORDER BY score) AS quartile, PERCENT_RANK() OVER (ORDER BY score) AS pr, CUME_DIST() OVER (ORDER BY score) AS cd FROM scores; +SELECT id, NTH_VALUE(amount, 2) OVER (PARTITION BY grp ORDER BY ts) AS second_amount FROM t; +SELECT id, RATIO_TO_REPORT(amount) OVER (PARTITION BY grp) AS share FROM t; +SELECT id, LAG(amount) IGNORE NULLS OVER (PARTITION BY grp ORDER BY ts) AS prev_non_null FROM t; +SELECT id, FIRST_VALUE(amount) IGNORE NULLS OVER (ORDER BY ts) AS fv FROM t; +WITH ranked AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS rn FROM employees) SELECT * FROM ranked WHERE rn <= 3; +WITH dedup AS (SELECT * FROM raw_events QUALIFY ROW_NUMBER() OVER (PARTITION BY id ORDER BY updated_at DESC) = 1), enriched AS (SELECT d.*, u.email FROM dedup d LEFT JOIN users u ON u.id = d.user_id) SELECT * FROM enriched WHERE email IS NOT NULL; +SELECT *, SUM(x) OVER () AS total, x / NULLIF(SUM(x) OVER (), 0) AS pct FROM t; +SELECT MIN_BY(name, score) AS worst, MAX_BY(name, score) AS best FROM players; +SELECT ANY_VALUE(name) AS n, MODE(country) AS mode_country FROM customers GROUP BY segment; diff --git a/testdata/realworld/snowflake/05_dialect_specific.sql b/testdata/realworld/snowflake/05_dialect_specific.sql new file mode 100644 index 00000000..bbc7fea4 --- /dev/null +++ b/testdata/realworld/snowflake/05_dialect_specific.sql @@ -0,0 +1,53 @@ +-- Snowflake-specific: VARIANT paths, FLATTEN, IFF, TRY_*, ILIKE ANY, SAMPLE, time travel +SELECT payload:user.id::number AS user_id, payload:user.name::string AS name FROM raw_events; +SELECT payload:items[0].sku::string AS first_sku, payload:items[1]:qty::int AS second_qty FROM raw_orders; +SELECT src:"customer id"::string AS cid, src['order']['total']::float AS total FROM raw_json; +SELECT GET_PATH(payload, 'a.b.c') AS deep, payload:a.b.c AS deep2 FROM t; +SELECT e.payload:id::string AS id, f.value:sku::string AS sku, f.index AS idx FROM raw_orders e, LATERAL FLATTEN(input => e.payload:items) f; +SELECT o.id, f.value:sku::string AS sku, f.value:qty::number AS qty FROM orders o, LATERAL FLATTEN(input => o.items, outer => TRUE) f; +SELECT f.key, f.value FROM TABLE(FLATTEN(input => PARSE_JSON('{"a":1,"b":2}'))) f; +SELECT t.id, f.value::string AS tag FROM tbl t, TABLE(FLATTEN(t.tags)) f; +SELECT id, f.path, f.value FROM events, LATERAL FLATTEN(input => payload, recursive => TRUE) f WHERE f.key = 'email'; +SELECT IFF(score >= 60, 'pass', 'fail') AS result, ZEROIFNULL(bonus) AS bonus, NULLIFZERO(qty) AS qty FROM exam; +SELECT TRY_TO_NUMBER(s) AS n, TRY_TO_DATE(d, 'YYYY-MM-DD') AS dt, TRY_TO_TIMESTAMP(ts) AS t, TRY_TO_BOOLEAN(b) AS bl, TRY_PARSE_JSON(j) AS js FROM raw_strings; +SELECT TO_VARIANT(x), TO_VARCHAR(y), TO_TIMESTAMP_NTZ(z), TO_DECIMAL(q, 10, 2) FROM t; +SELECT * FROM products WHERE name ILIKE ANY ('%shoe%', '%boot%'); +SELECT * FROM products WHERE name LIKE ANY ('a%', 'b%') AND sku NOT ILIKE ALL ('x%', 'y%'); +SELECT * FROM users WHERE email RLIKE '.*@example\\.com'; +SELECT * FROM users WHERE REGEXP_LIKE(email, '^[a-z]+@example\\.com$', 'i'); +SELECT * FROM orders AT (TIMESTAMP => '2024-06-01 12:00:00'::timestamp_tz); +SELECT * FROM orders AT (OFFSET => -60*5); +SELECT * FROM orders BEFORE (STATEMENT => '8e5d0ca9-005e-44e6-b858-a8f5b37c5726'); +SELECT * FROM orders AT (TIMESTAMP => DATEADD(hour, -1, CURRENT_TIMESTAMP())) o JOIN customers c ON c.id = o.customer_id; +SELECT * FROM big_table SAMPLE (10); +SELECT * FROM big_table TABLESAMPLE BERNOULLI (5); +SELECT * FROM big_table SAMPLE SYSTEM (1) SEED (42); +SELECT * FROM big_table SAMPLE (1000 ROWS); +SELECT $1, $2, $3 FROM @my_stage/data.csv (FILE_FORMAT => 'my_csv'); +SELECT t.$1 AS id, t.$2 AS name FROM @my_stage/users/ (FILE_FORMAT => 'my_csv', PATTERN => '.*[.]csv') t; +SELECT $1:id::number AS id, $1:name::string AS name FROM @json_stage (FILE_FORMAT => 'my_json'); +SELECT METADATA$FILENAME, METADATA$FILE_ROW_NUMBER, $1 FROM @my_stage; +SELECT * FROM TABLE(INFORMATION_SCHEMA.QUERY_HISTORY(RESULT_LIMIT => 100)); +SELECT * FROM TABLE(RESULT_SCAN(LAST_QUERY_ID())); +SELECT * FROM TABLE(GENERATOR(ROWCOUNT => 100)); +SELECT SEQ4() AS n, UNIFORM(1, 100, RANDOM()) AS r FROM TABLE(GENERATOR(ROWCOUNT => 10)); +SELECT OBJECT_CONSTRUCT('a', 1, 'b', 'two') AS o, ARRAY_CONSTRUCT(1, 2, 3) AS a, OBJECT_INSERT(OBJECT_CONSTRUCT(), 'k', 'v') AS o2; +SELECT ARRAY_SIZE(payload:items) AS n, ARRAY_CONTAINS('x'::variant, tags) AS has_x, ARRAY_TO_STRING(tags, ',') AS s FROM t; +SELECT OBJECT_KEYS(payload) AS ks, TYPEOF(payload:id) AS ty, IS_NULL_VALUE(payload:x) AS nv FROM t; +SELECT CURRENT_ACCOUNT(), CURRENT_REGION(), CURRENT_WAREHOUSE(), CURRENT_ROLE(), CURRENT_DATABASE(), CURRENT_SCHEMA(); +SELECT DATEADD(day, 7, CURRENT_DATE()), DATEDIFF(day, '2024-01-01', '2024-02-01'), LAST_DAY(CURRENT_DATE(), 'month'), DATE_FROM_PARTS(2024, 2, 29); +SELECT CONVERT_TIMEZONE('UTC', 'America/Los_Angeles', ts) AS la_time FROM events; +SELECT ts::date AS d, ts::time AS t, '123'::number(10,2) AS n, x::variant AS v FROM t; +SELECT EDITDISTANCE(a, b), JAROWINKLER_SIMILARITY(a, b), SOUNDEX(a), SPLIT_PART(email, '@', 2), STRTOK(path, '/', 2) FROM t; +SELECT HASH(a, b), MD5(a), SHA2(a, 256), BITAND(a, b), BOOLAND_AGG(flag), BITOR_AGG(mask) FROM t GROUP BY g; +SELECT * FROM t WHERE col IN (SELECT value FROM TABLE(FLATTEN(input => SPLIT('a,b,c', ',')))); +SELECT $$multi +line string$$ AS s; +SELECT 'it''s' AS q, 1e3 AS sci, .5 AS frac; +SELECT * FROM "My Table" WHERE "Mixed Case Col" = 1; +SELECT a.* EXCLUDE (secret), b.* RENAME (id AS b_id) FROM a JOIN b ON a.id = b.a_id; +SELECT * EXCLUDE (ssn, dob) FROM customers; +SELECT * ILIKE '%name%' FROM customers; +SELECT * REPLACE (UPPER(name) AS name) FROM customers; +SELECT {'a': 1, 'b': 'two'} AS obj, [1, 2, 3] AS arr; +SELECT * FROM t1 ASOF JOIN t2 MATCH_CONDITION (t1.ts >= t2.ts) ON t1.sym = t2.sym; diff --git a/testdata/realworld/snowflake/06_etl_merge.sql b/testdata/realworld/snowflake/06_etl_merge.sql new file mode 100644 index 00000000..ce71c83f --- /dev/null +++ b/testdata/realworld/snowflake/06_etl_merge.sql @@ -0,0 +1,37 @@ +-- ETL / ELT: MERGE upserts, COPY INTO, staging, streams and tasks +MERGE INTO dim_customer t USING stg_customer s ON t.customer_id = s.customer_id WHEN MATCHED THEN UPDATE SET name = s.name, email = s.email, updated_at = CURRENT_TIMESTAMP() WHEN NOT MATCHED THEN INSERT (customer_id, name, email, updated_at) VALUES (s.customer_id, s.name, s.email, CURRENT_TIMESTAMP()); +MERGE INTO target t USING source s ON t.id = s.id WHEN MATCHED AND s.deleted THEN DELETE WHEN MATCHED THEN UPDATE SET val = s.val WHEN NOT MATCHED THEN INSERT (id, val) VALUES (s.id, s.val); +MERGE INTO inventory i USING (SELECT sku, SUM(qty) AS qty FROM shipments GROUP BY sku) s ON i.sku = s.sku WHEN MATCHED THEN UPDATE SET qty = i.qty + s.qty WHEN NOT MATCHED AND s.qty > 0 THEN INSERT VALUES (s.sku, s.qty); +MERGE INTO t USING s ON t.k = s.k WHEN NOT MATCHED THEN INSERT (k, v) VALUES (s.k, s.v); +COPY INTO raw_events FROM @my_stage/events/ FILE_FORMAT = (TYPE = 'JSON') ON_ERROR = 'CONTINUE'; +COPY INTO raw_users (id, name) FROM (SELECT $1, $2 FROM @my_stage/users.csv) FILE_FORMAT = (FORMAT_NAME = 'my_csv'); +COPY INTO @export_stage/out/ FROM (SELECT * FROM orders WHERE order_date = CURRENT_DATE()) FILE_FORMAT = (TYPE = 'PARQUET') HEADER = TRUE OVERWRITE = TRUE; +COPY INTO mytable FROM @mystage PATTERN = '.*[.]csv' FORCE = TRUE; +INSERT INTO fact_orders SELECT o.id, o.customer_id, o.total, d.date_key FROM stg_orders o JOIN dim_date d ON d.d = o.order_date::date; +INSERT INTO t (a, b) SELECT a, b FROM s WHERE NOT EXISTS (SELECT 1 FROM t WHERE t.a = s.a); +INSERT OVERWRITE INTO report_daily SELECT CURRENT_DATE(), COUNT(*) FROM orders; +INSERT INTO events_json SELECT PARSE_JSON('{"a": 1}'); +INSERT INTO t SELECT * FROM TABLE(FLATTEN(input => ARRAY_CONSTRUCT(1, 2, 3))); +CREATE OR REPLACE STREAM orders_stream ON TABLE orders; +CREATE OR REPLACE STREAM s2 ON TABLE orders APPEND_ONLY = TRUE; +SELECT * FROM orders_stream WHERE METADATA$ACTION = 'INSERT'; +CREATE OR REPLACE TASK refresh_summary WAREHOUSE = etl_wh SCHEDULE = 'USING CRON 0 * * * * UTC' AS INSERT OVERWRITE INTO summary SELECT region, SUM(amount) FROM sales GROUP BY region; +CREATE TASK t2 WAREHOUSE = w AFTER refresh_summary AS CALL my_proc(); +ALTER TASK refresh_summary RESUME; +ALTER TASK refresh_summary SUSPEND; +CREATE OR REPLACE PIPE my_pipe AUTO_INGEST = TRUE AS COPY INTO raw_events FROM @my_stage; +CREATE OR REPLACE DYNAMIC TABLE dt_sales TARGET_LAG = '5 minutes' WAREHOUSE = etl_wh AS SELECT region, SUM(amount) AS total FROM sales GROUP BY region; +CREATE OR REPLACE TABLE dedup_events AS SELECT * FROM raw_events QUALIFY ROW_NUMBER() OVER (PARTITION BY id ORDER BY ts DESC) = 1; +UPDATE target SET status = s.status FROM source s WHERE target.id = s.id AND target.status <> s.status; +DELETE FROM target WHERE id IN (SELECT id FROM source WHERE op = 'D'); +LIST @my_stage; +REMOVE @my_stage/old/; +PUT file:///tmp/data.csv @my_stage AUTO_COMPRESS = TRUE; +GET @my_stage/out.csv file:///tmp/; +CALL my_proc(1, 'a'); +CALL db.schema.sp_load('2024-01-01'::date); +EXECUTE IMMEDIATE 'SELECT 1'; +BEGIN; +INSERT INTO t VALUES (1); +COMMIT; +ROLLBACK; diff --git a/testdata/realworld/snowflake/07_admin.sql b/testdata/realworld/snowflake/07_admin.sql new file mode 100644 index 00000000..502f9d93 --- /dev/null +++ b/testdata/realworld/snowflake/07_admin.sql @@ -0,0 +1,54 @@ +-- Admin / ops queries: grants, warehouses, session, metadata +USE ROLE SYSADMIN; +USE WAREHOUSE compute_wh; +USE DATABASE analytics; +USE SCHEMA analytics.public; +USE analytics.public; +SHOW TABLES; +SHOW TABLES IN SCHEMA analytics.public; +SHOW TABLES LIKE 'ORD%' IN DATABASE analytics; +SHOW SCHEMAS IN DATABASE analytics; +SHOW COLUMNS IN TABLE orders; +SHOW GRANTS TO ROLE analyst; +SHOW GRANTS ON TABLE orders; +SHOW WAREHOUSES; +SHOW USERS; +SHOW PARAMETERS LIKE 'TIMEZONE' IN SESSION; +SHOW VIEWS; +SHOW TASKS; +SHOW STAGES; +DESCRIBE TABLE orders; +DESC VIEW v_orders; +DESCRIBE STAGE my_stage; +CREATE WAREHOUSE IF NOT EXISTS etl_wh WITH WAREHOUSE_SIZE = 'XSMALL' AUTO_SUSPEND = 60 AUTO_RESUME = TRUE INITIALLY_SUSPENDED = TRUE; +ALTER WAREHOUSE etl_wh SET WAREHOUSE_SIZE = 'LARGE'; +ALTER WAREHOUSE etl_wh SUSPEND; +CREATE ROLE analyst; +GRANT ROLE analyst TO USER jdoe; +GRANT USAGE ON WAREHOUSE compute_wh TO ROLE analyst; +GRANT USAGE ON DATABASE analytics TO ROLE analyst; +GRANT SELECT ON ALL TABLES IN SCHEMA analytics.public TO ROLE analyst; +GRANT SELECT ON FUTURE TABLES IN SCHEMA analytics.public TO ROLE analyst; +GRANT ALL PRIVILEGES ON TABLE orders TO ROLE etl_role; +REVOKE SELECT ON TABLE orders FROM ROLE analyst; +CREATE USER jdoe PASSWORD = 'Temp#1234' DEFAULT_ROLE = analyst MUST_CHANGE_PASSWORD = TRUE; +ALTER USER jdoe SET DISABLED = TRUE; +ALTER SESSION SET TIMEZONE = 'UTC'; +ALTER SESSION SET QUERY_TAG = 'nightly_etl'; +ALTER SESSION SET TIMESTAMP_OUTPUT_FORMAT = 'YYYY-MM-DD HH24:MI:SS'; +SET my_var = 10; +SELECT $my_var + 1; +SELECT * FROM IDENTIFIER('orders') LIMIT 5; +SELECT * FROM snowflake.account_usage.query_history WHERE start_time > DATEADD(day, -1, CURRENT_TIMESTAMP()) ORDER BY total_elapsed_time DESC LIMIT 50; +SELECT warehouse_name, SUM(credits_used) AS credits FROM snowflake.account_usage.warehouse_metering_history WHERE start_time >= DATEADD(month, -1, CURRENT_DATE()) GROUP BY 1 ORDER BY 2 DESC; +SELECT table_schema, table_name, row_count, bytes / POWER(1024, 3) AS gb FROM information_schema.tables WHERE table_type = 'BASE TABLE' ORDER BY bytes DESC; +SELECT SYSTEM$CLUSTERING_INFORMATION('orders', '(order_date)'); +SELECT SYSTEM$TYPEOF(1); +SELECT GET_DDL('TABLE', 'orders'); +ALTER ACCOUNT SET TIMEZONE = 'UTC'; +ALTER TABLE orders ADD ROW ACCESS POLICY rap ON (region); +CREATE OR REPLACE MASKING POLICY email_mask AS (val STRING) RETURNS STRING -> CASE WHEN CURRENT_ROLE() IN ('ADMIN') THEN val ELSE '***' END; +ALTER TABLE users MODIFY COLUMN email SET MASKING POLICY email_mask; +CREATE TAG pii_tag; +ALTER TABLE users SET TAG pii_tag = 'true'; +EXPLAIN SELECT * FROM orders; diff --git a/testdata/realworld/snowflake/08_pivot_match.sql b/testdata/realworld/snowflake/08_pivot_match.sql new file mode 100644 index 00000000..73ce3b56 --- /dev/null +++ b/testdata/realworld/snowflake/08_pivot_match.sql @@ -0,0 +1,24 @@ +-- PIVOT, UNPIVOT, MATCH_RECOGNIZE, set-returning and semi-structured reshaping +SELECT * FROM monthly_sales PIVOT (SUM(amount) FOR month IN ('JAN', 'FEB', 'MAR')) AS p; +SELECT * FROM monthly_sales PIVOT (SUM(amount) FOR month IN ('JAN', 'FEB', 'MAR', 'APR')) AS p (emp_id, jan, feb, mar, apr); +SELECT * FROM quarterly_sales PIVOT (AVG(amount) FOR quarter IN (ANY ORDER BY quarter)); +SELECT * FROM (SELECT empid, month, amount FROM monthly_sales) PIVOT (SUM(amount) FOR month IN ('JAN', 'FEB')) ORDER BY empid; +SELECT * FROM monthly_sales UNPIVOT (sales FOR month IN (jan, feb, mar)) ORDER BY empid; +SELECT * FROM monthly_sales UNPIVOT INCLUDE NULLS (sales FOR month IN (jan, feb)) AS u; +SELECT * FROM stock_price MATCH_RECOGNIZE (PARTITION BY symbol ORDER BY ts MEASURES MATCH_NUMBER() AS match_num, FIRST(ts) AS start_ts, LAST(ts) AS end_ts ONE ROW PER MATCH AFTER MATCH SKIP PAST LAST ROW PATTERN (down+ up+) DEFINE down AS price < PREV(price), up AS price > PREV(price)); +SELECT * FROM events MATCH_RECOGNIZE (PARTITION BY user_id ORDER BY ts MEASURES COUNT(*) AS n ALL ROWS PER MATCH PATTERN (a b* c) DEFINE a AS action = 'view', b AS action = 'click', c AS action = 'buy') ORDER BY user_id; +SELECT region, SUM(CASE WHEN product = 'A' THEN amount ELSE 0 END) AS a_total, SUM(CASE WHEN product = 'B' THEN amount ELSE 0 END) AS b_total FROM sales GROUP BY region; +SELECT * FROM TABLE(SPLIT_TO_TABLE('a b c', ' ')); +SELECT s.seq, s.index, s.value FROM TABLE(SPLIT_TO_TABLE('x,y,z', ',')) s; +SELECT t.id, s.value FROM t, LATERAL SPLIT_TO_TABLE(t.csv, ',') s; +SELECT * FROM TABLE(FLATTEN(INPUT => ARRAY_CONSTRUCT('a', 'b'))) WHERE value::string <> 'a'; +SELECT * FROM VALUES (1, 'a'), (2, 'b') AS v (id, name); +SELECT column1, column2 FROM (VALUES (1, 'x'), (2, 'y')); +SELECT * FROM TABLE(my_udtf(1, 'a')); +SELECT * FROM my_table, LATERAL (SELECT * FROM other WHERE other.k = my_table.k) l; +SELECT OBJECT_AGG(k, v) AS obj FROM kv GROUP BY grp; +SELECT ARRAY_AGG(OBJECT_CONSTRUCT('id', id, 'name', name)) AS arr FROM users; +SELECT ARRAY_SORT(ARRAY_CONSTRUCT(3, 1, 2)), ARRAY_SLICE(ARRAY_CONSTRUCT(1, 2, 3, 4), 1, 3), ARRAY_DISTINCT(tags) FROM t; +SELECT t.v:a[0]:b::string AS x, ARRAY_SIZE(t.v:a) AS n FROM t; +SELECT PARSE_JSON(raw):meta.ts::timestamp_ntz AS ts FROM landing; +SELECT payload:data[0].attributes."first-name"::string AS fname FROM t; diff --git a/testdata/realworld/snowflake/09_dbt_models.sql b/testdata/realworld/snowflake/09_dbt_models.sql new file mode 100644 index 00000000..2052877d --- /dev/null +++ b/testdata/realworld/snowflake/09_dbt_models.sql @@ -0,0 +1,19 @@ +-- dbt-style models (Jinja already rendered): staging, intermediate, marts +WITH source AS (SELECT * FROM raw.stripe.payments), renamed AS (SELECT id AS payment_id, orderid AS order_id, paymentmethod AS payment_method, status, amount / 100 AS amount, created AS created_at FROM source) SELECT * FROM renamed; +WITH customers AS (SELECT * FROM analytics.staging.stg_customers), orders AS (SELECT * FROM analytics.staging.stg_orders), customer_orders AS (SELECT customer_id, MIN(order_date) AS first_order, MAX(order_date) AS most_recent_order, COUNT(order_id) AS number_of_orders FROM orders GROUP BY customer_id), final AS (SELECT customers.customer_id, customers.first_name, customers.last_name, customer_orders.first_order, customer_orders.most_recent_order, COALESCE(customer_orders.number_of_orders, 0) AS number_of_orders FROM customers LEFT JOIN customer_orders USING (customer_id)) SELECT * FROM final; +CREATE OR REPLACE TABLE analytics.marts.fct_orders AS WITH orders AS (SELECT * FROM analytics.staging.stg_orders), payments AS (SELECT * FROM analytics.staging.stg_payments), order_payments AS (SELECT order_id, SUM(CASE WHEN payment_method = 'credit_card' THEN amount ELSE 0 END) AS credit_card_amount, SUM(CASE WHEN payment_method = 'bank_transfer' THEN amount ELSE 0 END) AS bank_transfer_amount, SUM(amount) AS total_amount FROM payments WHERE status = 'success' GROUP BY order_id) SELECT o.order_id, o.customer_id, o.order_date, op.credit_card_amount, op.bank_transfer_amount, op.total_amount AS amount FROM orders o LEFT JOIN order_payments op ON o.order_id = op.order_id; +CREATE OR REPLACE VIEW analytics.staging.stg_customers AS SELECT id AS customer_id, first_name, last_name FROM raw.jaffle_shop.customers; +SELECT date_day, COUNT(*) AS n FROM (SELECT DATEADD(day, ROW_NUMBER() OVER (ORDER BY SEQ4()) - 1, '2020-01-01'::date) AS date_day FROM TABLE(GENERATOR(ROWCOUNT => 1000))) GROUP BY 1; +SELECT MD5(CAST(COALESCE(CAST(customer_id AS VARCHAR), '') || '-' || COALESCE(CAST(order_id AS VARCHAR), '') AS VARCHAR)) AS surrogate_key, * FROM analytics.staging.stg_orders; +SELECT * FROM analytics.marts.fct_orders WHERE order_date > (SELECT MAX(order_date) FROM analytics.marts.fct_orders_incremental); +DELETE FROM analytics.marts.fct_orders_incremental WHERE (order_id) IN (SELECT order_id FROM analytics.marts.fct_orders_incremental__tmp); +INSERT INTO analytics.marts.fct_orders_incremental (order_id, customer_id, order_date) SELECT order_id, customer_id, order_date FROM analytics.marts.fct_orders_incremental__tmp; +SELECT COUNT(*) AS failures, COUNT(*) != 0 AS should_warn FROM (SELECT customer_id FROM analytics.marts.dim_customers WHERE customer_id IS NULL) dbt_internal_test; +SELECT customer_id AS unique_field, COUNT(*) AS n_records FROM analytics.marts.dim_customers WHERE customer_id IS NOT NULL GROUP BY customer_id HAVING COUNT(*) > 1; +SELECT a.*, b.* FROM (SELECT 1 AS id) a LEFT JOIN (SELECT 1 AS id) b ON a.id = b.id WHERE b.id IS NULL; +CREATE OR REPLACE TRANSIENT TABLE analytics.snapshots.orders_snapshot AS SELECT *, CURRENT_TIMESTAMP() AS dbt_valid_from, NULL::timestamp_ntz AS dbt_valid_to FROM analytics.staging.stg_orders; +SELECT order_id, status, LAG(status) OVER (PARTITION BY order_id ORDER BY updated_at) AS prev_status FROM analytics.staging.stg_order_events QUALIFY status != prev_status; +SELECT DATE_TRUNC('week', order_date)::date AS week_start, SUM(amount) AS weekly_amount, SUM(SUM(amount)) OVER (ORDER BY DATE_TRUNC('week', order_date)) AS cumulative FROM analytics.marts.fct_orders GROUP BY 1; +ALTER TABLE analytics.marts.fct_orders SWAP WITH analytics.marts.fct_orders__dbt_tmp; +DROP TABLE IF EXISTS analytics.marts.fct_orders__dbt_tmp CASCADE; +GRANT SELECT ON TABLE analytics.marts.fct_orders TO ROLE reporter; diff --git a/testdata/realworld/snowflake/10_complex_nested.sql b/testdata/realworld/snowflake/10_complex_nested.sql new file mode 100644 index 00000000..ec2464c9 --- /dev/null +++ b/testdata/realworld/snowflake/10_complex_nested.sql @@ -0,0 +1,41 @@ +-- Deeply nested subqueries, long CTE chains, many joins, expression-heavy queries +SELECT * FROM (SELECT * FROM (SELECT * FROM (SELECT * FROM (SELECT id, name FROM users WHERE active) a WHERE id > 10) b WHERE id < 1000) c ORDER BY id) d LIMIT 10; +SELECT o.id, c.name, p.name, s.name, w.name, r.name FROM orders o JOIN customers c ON c.id = o.customer_id JOIN order_items oi ON oi.order_id = o.id JOIN products p ON p.id = oi.product_id JOIN suppliers s ON s.id = p.supplier_id JOIN warehouses w ON w.id = oi.warehouse_id LEFT JOIN regions r ON r.id = w.region_id LEFT JOIN promotions pr ON pr.id = o.promo_id WHERE o.status <> 'cancelled' AND (pr.id IS NULL OR pr.expires_at > o.created_at); +WITH a AS (SELECT 1 AS x), b AS (SELECT x + 1 AS x FROM a), c AS (SELECT x + 1 AS x FROM b), d AS (SELECT x + 1 AS x FROM c), e AS (SELECT x + 1 AS x FROM d), f AS (SELECT x + 1 AS x FROM e), g AS (SELECT x + 1 AS x FROM f), h AS (SELECT x + 1 AS x FROM g) SELECT * FROM h; +WITH RECURSIVE org AS (SELECT id, manager_id, name, 1 AS lvl FROM employees WHERE manager_id IS NULL UNION ALL SELECT e.id, e.manager_id, e.name, o.lvl + 1 FROM employees e JOIN org o ON e.manager_id = o.id) SELECT * FROM org ORDER BY lvl, name; +WITH RECURSIVE nums (n) AS (SELECT 1 UNION ALL SELECT n + 1 FROM nums WHERE n < 10) SELECT n FROM nums; +SELECT id, (SELECT MAX(amount) FROM payments p WHERE p.order_id = o.id) AS max_payment, (SELECT COUNT(*) FROM order_items i WHERE i.order_id = o.id) AS n_items FROM orders o WHERE o.id IN (SELECT order_id FROM payments WHERE amount > (SELECT AVG(amount) FROM payments)); +SELECT * FROM t WHERE (a, b) IN (SELECT x, y FROM s) AND c NOT IN (SELECT z FROM u WHERE z IS NOT NULL); +SELECT * FROM t WHERE a = ANY (SELECT x FROM s) AND b > ALL (SELECT y FROM u); +SELECT * FROM t WHERE EXISTS (SELECT 1 FROM s WHERE s.k = t.k AND NOT EXISTS (SELECT 1 FROM u WHERE u.k = s.k)); +SELECT CASE WHEN a > 0 THEN CASE WHEN b > 0 THEN 'pp' ELSE 'pn' END WHEN a < 0 THEN CASE WHEN b > 0 THEN 'np' ELSE 'nn' END ELSE 'z' END AS quadrant FROM t; +SELECT a + b * c - d / e % f AS arith, (a + b) * (c - d) AS paren, -a AS neg, NOT a AS notv, a || b || c AS cat FROM t; +SELECT * FROM t WHERE a = 1 OR b = 2 AND c = 3 OR NOT d = 4 AND (e = 5 OR f = 6); +SELECT * FROM t WHERE x BETWEEN 1 AND 10 AND y NOT BETWEEN 5 AND 6 AND z IS DISTINCT FROM w AND q IS NOT DISTINCT FROM r; +SELECT a, b FROM t1 UNION SELECT a, b FROM t2 UNION ALL SELECT a, b FROM t3 ORDER BY a LIMIT 5; +(SELECT a FROM t1 ORDER BY a LIMIT 3) UNION ALL (SELECT a FROM t2 ORDER BY a DESC LIMIT 3); +SELECT a FROM t1 WHERE a IN (SELECT a FROM t2 UNION SELECT a FROM t3); +SELECT x.* FROM (SELECT a, b FROM t1 UNION ALL SELECT a, b FROM t2) x JOIN (SELECT a FROM t3 INTERSECT SELECT a FROM t4) y ON x.a = y.a; +WITH base AS (SELECT user_id, event_ts, event_name, payload FROM raw.events WHERE event_ts >= DATEADD(day, -30, CURRENT_DATE())), sessions AS (SELECT user_id, event_ts, SUM(is_new) OVER (PARTITION BY user_id ORDER BY event_ts) AS session_id FROM (SELECT user_id, event_ts, IFF(DATEDIFF(minute, LAG(event_ts) OVER (PARTITION BY user_id ORDER BY event_ts), event_ts) > 30, 1, 0) AS is_new FROM base)), agg AS (SELECT user_id, session_id, MIN(event_ts) AS s_start, MAX(event_ts) AS s_end, COUNT(*) AS events FROM sessions GROUP BY 1, 2) SELECT user_id, COUNT(*) AS sessions, AVG(DATEDIFF(second, s_start, s_end)) AS avg_secs FROM agg GROUP BY user_id HAVING COUNT(*) > 1; +SELECT t.id, t.payload:a::string AS a, f1.value:b::string AS b, f2.value::number AS c FROM tbl t, LATERAL FLATTEN(input => t.payload:list) f1, LATERAL FLATTEN(input => f1.value:nums) f2 WHERE f2.value::number > 0; +SELECT u.id, ARRAY_AGG(DISTINCT p.name) WITHIN GROUP (ORDER BY p.name) AS products, SUM(oi.qty * oi.price) AS spend FROM users u JOIN orders o ON o.user_id = u.id JOIN order_items oi ON oi.order_id = o.id JOIN products p ON p.id = oi.product_id GROUP BY u.id QUALIFY RANK() OVER (ORDER BY SUM(oi.qty * oi.price) DESC) <= 10; +SELECT COALESCE(a.id, b.id) AS id, a.v AS a_v, b.v AS b_v FROM a FULL JOIN b ON a.id = b.id WHERE COALESCE(a.v, '') <> COALESCE(b.v, ''); +SELECT CAST(x AS NUMBER(10, 2)), x::varchar(10), TRY_CAST(y AS INT), CAST('2024-01-01' AS DATE), EXTRACT(year FROM d), EXTRACT(epoch_second FROM ts), DATE_PART(dayofweek, d) FROM t; +SELECT SUBSTR(s, 2, 3), LEFT(s, 2), RIGHT(s, 2), TRIM(s), LTRIM(s, ' '), REPLACE(s, 'a', 'b'), POSITION('a' IN s), CHARINDEX('a', s), LENGTH(s), UPPER(s), INITCAP(s), LPAD(s, 5, '0'), REGEXP_REPLACE(s, '[0-9]+', ''), REGEXP_SUBSTR(s, '[a-z]+', 1, 1, 'i'), CONTAINS(s, 'x'), STARTSWITH(s, 'a') FROM t; +SELECT INTERVAL '1 day' + CURRENT_TIMESTAMP(), CURRENT_DATE() - INTERVAL '3 months', ts + INTERVAL '2 hours 30 minutes' FROM t; +SELECT ROUND(x, 2), CEIL(x), FLOOR(x), ABS(x), MOD(x, 3), POWER(x, 2), SQRT(x), LN(x), GREATEST(a, b, c), LEAST(a, b), DIV0(a, b), SIGN(x), TRUNC(x, 1) FROM t; +SELECT * FROM a, b, c WHERE a.id = b.id AND b.id = c.id; +SELECT * FROM (SELECT 1 AS a) AS x(a) JOIN (SELECT 2 AS b) AS y(b) ON TRUE; +SELECT 1 AS "select", 2 AS "from", 3 AS "Group By"; +SELECT t."ID", t."Name" FROM "DB"."SCH"."T" AS t; +SELECT a FROM t ORDER BY a NULLS LAST, b DESC NULLS FIRST; +SELECT a FROM t LIMIT 10 OFFSET 5; +SELECT a FROM t FETCH FIRST 5 ROWS ONLY; +SELECT a FROM t OFFSET 5 ROWS FETCH NEXT 10 ROWS ONLY; +SELECT * FROM t1 JOIN t2 ON t1.id = t2.id AND t1.ts BETWEEN t2.start_ts AND t2.end_ts; +SELECT * FROM t1 ASOF JOIN t2 MATCH_CONDITION (t1.ts >= t2.ts) ON t1.sym = t2.sym; +SELECT * FROM t1 LEFT JOIN LATERAL (SELECT * FROM t2 WHERE t2.k = t1.k ORDER BY ts DESC LIMIT 1) l ON TRUE; +SELECT ARRAY_AGG(x) WITHIN GROUP (ORDER BY y DESC) FROM t; +SELECT SUM(x) OVER (PARTITION BY a, b ORDER BY c DESC, d ASC ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) FROM t; +SELECT SUM(x) OVER (ORDER BY ts RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) FROM t; +SELECT a FROM t WHERE b = 'x' AND c IN (1, 2, 3) AND d LIKE 'a%' ESCAPE '\\' AND e IS NULL; diff --git a/testdata/realworld/sqlite/01_app_crud.sql b/testdata/realworld/sqlite/01_app_crud.sql new file mode 100644 index 00000000..b78664ff --- /dev/null +++ b/testdata/realworld/sqlite/01_app_crud.sql @@ -0,0 +1,37 @@ +-- Scenario: ORM-style CRUD for a notes/todo mobile app +CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + email TEXT NOT NULL UNIQUE, + display_name TEXT, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + is_active INTEGER NOT NULL DEFAULT 1 +); +CREATE TABLE IF NOT EXISTS notes ( + id INTEGER PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + title TEXT NOT NULL, + body TEXT, + pinned INTEGER NOT NULL DEFAULT 0, + updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP +); +INSERT INTO users (email, display_name) VALUES ('a@example.com', 'Alice'); +INSERT INTO users (email, display_name) VALUES (?, ?); +INSERT INTO users (email, display_name) VALUES (:email, :name); +INSERT INTO notes (user_id, title, body) VALUES (1, 'Groceries', 'milk, eggs'), (1, 'Todo', 'ship it'); +SELECT id, email, display_name FROM users WHERE id = ?; +SELECT * FROM notes WHERE user_id = ?1 ORDER BY pinned DESC, updated_at DESC LIMIT 20 OFFSET 40; +SELECT * FROM notes WHERE title LIKE '%' || ? || '%' ESCAPE '\' ORDER BY id; +SELECT count(*) FROM notes WHERE user_id = ? AND pinned = 1; +UPDATE notes SET title = ?, body = ?, updated_at = datetime('now') WHERE id = ? AND user_id = ?; +UPDATE users SET is_active = 0 WHERE id IN (SELECT user_id FROM notes GROUP BY user_id HAVING count(*) = 0); +DELETE FROM notes WHERE id = ? AND user_id = ?; +DELETE FROM notes WHERE updated_at < datetime('now', '-90 days'); +INSERT INTO users (email, display_name) VALUES ('b@example.com', 'Bob') RETURNING id; +UPDATE notes SET pinned = 1 WHERE id = 5 RETURNING id, title, pinned; +DELETE FROM notes WHERE user_id = 9 RETURNING *; +SELECT n.id, n.title, u.display_name FROM notes AS n JOIN users AS u ON u.id = n.user_id WHERE u.is_active = 1; +SELECT n.id, u.email FROM notes n LEFT JOIN users u ON u.id = n.user_id WHERE u.id IS NULL; +SELECT EXISTS (SELECT 1 FROM users WHERE email = ?); +SELECT last_insert_rowid(); +SELECT changes(); +INSERT INTO notes (user_id, title) SELECT id, 'Welcome' FROM users WHERE id NOT IN (SELECT user_id FROM notes); diff --git a/testdata/realworld/sqlite/02_upsert_replace.sql b/testdata/realworld/sqlite/02_upsert_replace.sql new file mode 100644 index 00000000..48a6a606 --- /dev/null +++ b/testdata/realworld/sqlite/02_upsert_replace.sql @@ -0,0 +1,32 @@ +-- Scenario: sync layer using SQLite conflict handling clauses +CREATE TABLE IF NOT EXISTS settings ( + key TEXT PRIMARY KEY, + value TEXT, + updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now')) +) WITHOUT ROWID; +CREATE TABLE IF NOT EXISTS counters ( + name TEXT PRIMARY KEY, + hits INTEGER NOT NULL DEFAULT 0 +); +CREATE TABLE IF NOT EXISTS tags ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL COLLATE NOCASE UNIQUE +); +INSERT OR REPLACE INTO settings (key, value) VALUES ('theme', 'dark'); +INSERT OR IGNORE INTO tags (name) VALUES ('work'); +INSERT OR ABORT INTO tags (name) VALUES ('home'); +INSERT OR FAIL INTO tags (name) VALUES ('misc'); +INSERT OR ROLLBACK INTO tags (name) VALUES ('later'); +REPLACE INTO settings (key, value) VALUES ('lang', 'en'); +INSERT INTO counters (name, hits) VALUES ('home', 1) ON CONFLICT (name) DO UPDATE SET hits = hits + 1; +INSERT INTO counters (name, hits) VALUES ('home', 1) ON CONFLICT(name) DO UPDATE SET hits = counters.hits + excluded.hits; +INSERT INTO tags (name) VALUES ('x') ON CONFLICT DO NOTHING; +INSERT INTO tags (name) VALUES ('x') ON CONFLICT (name) DO NOTHING; +INSERT INTO settings (key, value) VALUES ('a', '1') ON CONFLICT (key) DO UPDATE SET value = excluded.value, updated_at = strftime('%s','now') WHERE settings.value <> excluded.value; +INSERT INTO counters (name, hits) VALUES ('x', 1) ON CONFLICT (name) DO UPDATE SET hits = hits + 1 RETURNING name, hits; +INSERT INTO counters (name, hits) SELECT name, 1 FROM tags WHERE true ON CONFLICT (name) DO UPDATE SET hits = hits + 1; +UPDATE OR IGNORE tags SET name = 'new' WHERE id = 3; +UPDATE OR REPLACE settings SET key = 'k2' WHERE key = 'k1'; +INSERT INTO settings DEFAULT VALUES; +INSERT INTO settings (key, value) VALUES ('p', 'q') ON CONFLICT (key) WHERE value IS NOT NULL DO NOTHING; +INSERT INTO counters (name, hits) VALUES ('c', 1), ('d', 2) ON CONFLICT (name) DO UPDATE SET hits = excluded.hits RETURNING *; diff --git a/testdata/realworld/sqlite/03_ddl_migrations.sql b/testdata/realworld/sqlite/03_ddl_migrations.sql new file mode 100644 index 00000000..54beb834 --- /dev/null +++ b/testdata/realworld/sqlite/03_ddl_migrations.sql @@ -0,0 +1,74 @@ +-- Scenario: schema migrations for an Electron app +CREATE TABLE IF NOT EXISTS schema_migrations ( + version INTEGER PRIMARY KEY, + applied_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP +); +CREATE TABLE accounts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + balance REAL NOT NULL DEFAULT 0.0 CHECK (balance >= 0), + currency TEXT NOT NULL DEFAULT 'USD' CHECK (length(currency) = 3), + meta BLOB +); +CREATE TABLE transactions ( + id INTEGER PRIMARY KEY, + account_id INTEGER NOT NULL, + amount NUMERIC NOT NULL, + memo TEXT, + posted_at TEXT NOT NULL, + FOREIGN KEY (account_id) REFERENCES accounts (id) ON UPDATE CASCADE ON DELETE RESTRICT +); +CREATE TABLE strict_events ( + id INTEGER PRIMARY KEY, + kind TEXT NOT NULL, + payload TEXT, + ts INTEGER NOT NULL +) STRICT; +CREATE TABLE strict_wr ( + k TEXT PRIMARY KEY, + v ANY +) STRICT, WITHOUT ROWID; +CREATE TABLE typeless (a, b, c); +CREATE TABLE gen ( + a INTEGER, + b INTEGER, + c INTEGER GENERATED ALWAYS AS (a + b) VIRTUAL, + d INTEGER AS (a * b) STORED +); +CREATE TEMP TABLE scratch (id INTEGER, v TEXT); +CREATE TEMPORARY TABLE IF NOT EXISTS scratch2 (id INTEGER); +CREATE TABLE copy_of_accounts AS SELECT * FROM accounts WHERE balance > 0; +CREATE INDEX IF NOT EXISTS idx_tx_account ON transactions (account_id, posted_at DESC); +CREATE UNIQUE INDEX uq_accounts_name ON accounts (name COLLATE NOCASE); +CREATE INDEX idx_tx_big ON transactions (amount) WHERE amount > 1000; +CREATE INDEX idx_tx_month ON transactions (strftime('%Y-%m', posted_at)); +CREATE VIEW IF NOT EXISTS v_account_totals AS + SELECT a.id, a.name, coalesce(sum(t.amount), 0) AS total FROM accounts a LEFT JOIN transactions t ON t.account_id = a.id GROUP BY a.id, a.name; +CREATE TEMP VIEW v_tmp AS SELECT 1 AS one; +ALTER TABLE accounts ADD COLUMN archived INTEGER NOT NULL DEFAULT 0; +ALTER TABLE accounts RENAME COLUMN name TO title; +ALTER TABLE accounts RENAME TO accounts_old; +ALTER TABLE transactions DROP COLUMN memo; +DROP TABLE IF EXISTS scratch; +DROP INDEX IF EXISTS idx_tx_big; +DROP VIEW IF EXISTS v_tmp; +CREATE TRIGGER trg_tx_after_insert AFTER INSERT ON transactions +BEGIN + UPDATE accounts SET balance = balance + NEW.amount WHERE id = NEW.account_id; +END; +CREATE TRIGGER IF NOT EXISTS trg_notes_touch BEFORE UPDATE OF title, body ON notes FOR EACH ROW WHEN NEW.title <> OLD.title +BEGIN + UPDATE notes SET updated_at = datetime('now') WHERE id = NEW.id; +END; +DROP TRIGGER IF EXISTS trg_notes_touch; +CREATE VIRTUAL TABLE docs_fts USING fts5(title, body); +BEGIN; +BEGIN IMMEDIATE; +COMMIT; +BEGIN TRANSACTION; +ROLLBACK; +SAVEPOINT sp1; +RELEASE SAVEPOINT sp1; +REINDEX idx_tx_account; +ANALYZE; +VACUUM; diff --git a/testdata/realworld/sqlite/04_window_analytics.sql b/testdata/realworld/sqlite/04_window_analytics.sql new file mode 100644 index 00000000..7f509faf --- /dev/null +++ b/testdata/realworld/sqlite/04_window_analytics.sql @@ -0,0 +1,26 @@ +-- Scenario: analytics dashboard with window functions +SELECT id, amount, sum(amount) OVER (ORDER BY posted_at) AS running FROM transactions; +SELECT account_id, posted_at, amount, row_number() OVER (PARTITION BY account_id ORDER BY posted_at DESC) AS rn FROM transactions; +SELECT *, rank() OVER w AS r, dense_rank() OVER w AS dr FROM transactions WINDOW w AS (PARTITION BY account_id ORDER BY amount DESC); +SELECT id, lag(amount, 1, 0) OVER (PARTITION BY account_id ORDER BY posted_at) AS prev, lead(amount) OVER (PARTITION BY account_id ORDER BY posted_at) AS nxt FROM transactions; +SELECT id, avg(amount) OVER (ORDER BY posted_at ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7 FROM transactions; +SELECT id, sum(amount) OVER (ORDER BY id RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) FROM transactions; +SELECT id, sum(amount) OVER (ORDER BY id GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) FROM transactions; +SELECT id, first_value(amount) OVER (PARTITION BY account_id ORDER BY posted_at ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) FROM transactions; +SELECT id, last_value(amount) OVER (PARTITION BY account_id ORDER BY posted_at) FROM transactions; +SELECT id, nth_value(amount, 2) OVER (ORDER BY posted_at) FROM transactions; +SELECT id, ntile(4) OVER (ORDER BY amount) AS quartile, percent_rank() OVER (ORDER BY amount) AS pr, cume_dist() OVER (ORDER BY amount) AS cd FROM transactions; +SELECT id, sum(amount) FILTER (WHERE amount > 0) OVER (PARTITION BY account_id) AS pos_total FROM transactions; +SELECT count(*) FILTER (WHERE amount < 0) AS refunds, count(*) AS total FROM transactions; +SELECT id, sum(amount) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING EXCLUDE CURRENT ROW) FROM transactions; +SELECT id, sum(amount) OVER (w1 ROWS 2 PRECEDING) FROM transactions WINDOW w1 AS (PARTITION BY account_id ORDER BY id); +WITH ranked AS ( + SELECT account_id, amount, row_number() OVER (PARTITION BY account_id ORDER BY amount DESC) AS rn FROM transactions +) +SELECT account_id, amount FROM ranked WHERE rn <= 3; +SELECT strftime('%Y-%m', posted_at) AS month, sum(amount) AS total, sum(sum(amount)) OVER (ORDER BY strftime('%Y-%m', posted_at)) AS cumulative FROM transactions GROUP BY month; +SELECT group_concat(name, ', ') FROM accounts; +SELECT account_id, group_concat(DISTINCT memo) FROM transactions GROUP BY account_id; +SELECT account_id, group_concat(memo, '; ' ) FROM transactions GROUP BY account_id HAVING count(*) > 2 ORDER BY account_id; +SELECT total(amount), min(amount), max(amount), avg(amount) FROM transactions; +SELECT string_agg(name, ',') FROM accounts; diff --git a/testdata/realworld/sqlite/05_dialect_specific.sql b/testdata/realworld/sqlite/05_dialect_specific.sql new file mode 100644 index 00000000..08c6d09c --- /dev/null +++ b/testdata/realworld/sqlite/05_dialect_specific.sql @@ -0,0 +1,70 @@ +-- Scenario: SQLite-specific syntax and functions +SELECT * FROM files WHERE path GLOB '*.txt'; +SELECT * FROM files WHERE path NOT GLOB '[a-c]*'; +SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'; +SELECT name, sql FROM sqlite_schema WHERE type IN ('table', 'index'); +SELECT typeof(x), quote(x), hex(x), zeroblob(16), randomblob(8) FROM t; +SELECT ifnull(a, 0), nullif(a, ''), coalesce(a, b, 'n/a') FROM t; +SELECT iif(a > 1, 'big', 'small') FROM t; +SELECT CASE WHEN a > 1 THEN 'x' WHEN a = 1 THEN 'y' ELSE 'z' END FROM t; +SELECT CASE a WHEN 1 THEN 'one' WHEN 2 THEN 'two' END FROM t; +SELECT a FROM t WHERE a IS NOT b; +SELECT a FROM t WHERE a IS b; +SELECT a FROM t WHERE a IS DISTINCT FROM b; +SELECT a FROM t WHERE a IS NOT DISTINCT FROM b; +SELECT a FROM t WHERE a BETWEEN 1 AND 10 AND b NOT BETWEEN 2 AND 3; +SELECT a FROM t WHERE a ISNULL; +SELECT a FROM t WHERE a NOTNULL; +SELECT a FROM t WHERE a == 1 OR a != 2 OR a <> 3; +SELECT 'it''s' AS s, x'DEADBEEF' AS b, 0xFF AS h, 1e3 AS f; +SELECT "quoted col", [bracket col], `backtick col` FROM "my table"; +SELECT a || b || c FROM t; +SELECT a & b, a | b, ~a, a << 2, a >> 1, a % 3 FROM t; +SELECT CAST(a AS INTEGER), CAST(b AS TEXT), CAST(c AS REAL), CAST(d AS NUMERIC), CAST(e AS BLOB) FROM t; +SELECT a REGEXP '^foo' FROM t; +SELECT a COLLATE NOCASE FROM t ORDER BY a COLLATE BINARY; +SELECT a FROM t ORDER BY a NULLS LAST; +SELECT a FROM t ORDER BY a DESC NULLS FIRST, b ASC; +SELECT a FROM t LIMIT 10, 20; +SELECT a FROM t LIMIT -1 OFFSET 5; +SELECT DISTINCT a FROM t; +SELECT ALL a FROM t; +SELECT a FROM t INDEXED BY idx_a WHERE a = 1; +SELECT a FROM t NOT INDEXED; +SELECT * FROM t1 NATURAL JOIN t2; +SELECT * FROM t1 JOIN t2 USING (id); +SELECT * FROM t1 CROSS JOIN t2; +SELECT * FROM t1, t2 WHERE t1.id = t2.id; +SELECT * FROM t1 LEFT OUTER JOIN t2 ON t1.id = t2.id; +SELECT * FROM t1 NATURAL LEFT JOIN t2; +SELECT * FROM t1 RIGHT JOIN t2 ON t1.id = t2.id; +SELECT * FROM t1 FULL OUTER JOIN t2 ON t1.id = t2.id; +SELECT * FROM main.t1; +SELECT * FROM (SELECT 1 AS a UNION ALL SELECT 2 UNION SELECT 3 INTERSECT SELECT 3 EXCEPT SELECT 2); +VALUES (1, 'a'), (2, 'b'); +SELECT * FROM pragma_table_info('users'); +SELECT * FROM generate_series(1, 10); +SELECT sqlite_version(), sqlite_source_id(); +SELECT unixepoch(), unixepoch('now', 'start of day'); +SELECT date('now'), time('now'), datetime('now', 'localtime'), julianday('now'); +SELECT datetime('now', '+1 day', 'start of month', '-1 second'); +SELECT strftime('%Y-%m-%dT%H:%M:%fZ', 'now'); +SELECT datetime(created_at, 'unixepoch') FROM events; +SELECT timediff('2024-01-02', '2024-01-01'); +SELECT abs(-1), round(3.14159, 2), max(1, 2, 3), min(1, 2), length('abc'), substr('hello', 2, 3), upper('a'), lower('A'), trim(' x '), ltrim('x', 'x'), replace('abc', 'b', 'X'), instr('abc', 'b'), printf('%05.1f', 3.14159), format('%d', 5); +SELECT unhex('4142'), likely(a), unlikely(a), likelihood(a, 0.5) FROM t; +SELECT random(), abs(random()) % 100; +SELECT a FROM t WHERE rowid = 5; +SELECT rowid, _rowid_, oid, * FROM t; +SELECT a FROM t WHERE (a, b) IN (SELECT a, b FROM u); +SELECT (1, 2) = (1, 2); +SELECT a FROM t WHERE a IN (); +SELECT a FROM t WHERE a IN (1, 2, 3) AND b NOT IN (SELECT id FROM u); +SELECT a FROM t WHERE a IN u; +SELECT * FROM t WHERE NOT EXISTS (SELECT 1 FROM u WHERE u.id = t.id); +SELECT (SELECT max(id) FROM u) AS mx, a FROM t; +SELECT a FROM t WHERE a LIKE 'x%' OR a NOT LIKE 'y_'; +SELECT a FROM t WHERE a MATCH 'foo'; +SELECT -a, +a, NOT a FROM t; +SELECT a FROM t GROUP BY a HAVING count(*) > 1; +SELECT a, count(*) AS c FROM t GROUP BY 1 ORDER BY 2 DESC; diff --git a/testdata/realworld/sqlite/06_json.sql b/testdata/realworld/sqlite/06_json.sql new file mode 100644 index 00000000..309ff13d --- /dev/null +++ b/testdata/realworld/sqlite/06_json.sql @@ -0,0 +1,29 @@ +-- Scenario: JSON-heavy document storage app +CREATE TABLE IF NOT EXISTS events (id INTEGER PRIMARY KEY, payload TEXT NOT NULL CHECK (json_valid(payload))); +INSERT INTO events (payload) VALUES ('{"type":"click","user":{"id":7,"name":"Zed"},"tags":["a","b"]}'); +INSERT INTO events (payload) VALUES (json_object('type', 'view', 'n', 3)); +SELECT json_extract(payload, '$.type') FROM events; +SELECT json_extract(payload, '$.user.id', '$.user.name') FROM events; +SELECT payload -> 'type' FROM events; +SELECT payload ->> 'type' FROM events; +SELECT payload -> '$.user' ->> '$.name' FROM events; +SELECT payload ->> '$.tags[0]' AS first_tag FROM events; +SELECT id FROM events WHERE payload ->> 'type' = 'click'; +SELECT id FROM events WHERE json_extract(payload, '$.user.id') = 7 AND payload ->> '$.n' > 2; +SELECT json_array(1, 2, 'x'), json_array_length('[1,2,3]'), json_type(payload, '$.user') FROM events; +SELECT json_set(payload, '$.seen', 1), json_insert(payload, '$.a', 2), json_replace(payload, '$.type', 'x'), json_remove(payload, '$.tags') FROM events; +SELECT json_patch(payload, '{"a":null}') FROM events; +SELECT json_group_array(payload ->> 'type') FROM events; +SELECT json_group_object(id, payload ->> 'type') FROM events; +SELECT e.id, j.value FROM events e, json_each(e.payload, '$.tags') AS j; +SELECT e.id, j.key, j.value, j.type FROM events AS e JOIN json_each(e.payload) AS j ON 1 = 1; +SELECT j.fullkey, j.atom FROM events e, json_tree(e.payload) j WHERE j.atom IS NOT NULL; +SELECT jsonb(payload), jsonb_extract(payload, '$.type'), json(jsonb(payload)) FROM events; +SELECT jsonb_set(jsonb(payload), '$.k', 'v') FROM events; +CREATE INDEX idx_events_type ON events (json_extract(payload, '$.type')); +CREATE INDEX idx_events_type2 ON events ((payload ->> '$.type')); +UPDATE events SET payload = json_set(payload, '$.processed', json('true')) WHERE payload ->> 'type' = 'click'; +UPDATE events SET payload = json_remove(payload, '$.user.name') WHERE id = 1 RETURNING id, payload; +SELECT json_quote('x'), json_valid(payload) FROM events; +WITH t AS (SELECT payload ->> 'type' AS type, count(*) AS n FROM events GROUP BY 1) +SELECT type, n, round(100.0 * n / sum(n) OVER (), 1) AS pct FROM t ORDER BY n DESC; diff --git a/testdata/realworld/sqlite/07_ctes_recursive.sql b/testdata/realworld/sqlite/07_ctes_recursive.sql new file mode 100644 index 00000000..e59dc810 --- /dev/null +++ b/testdata/realworld/sqlite/07_ctes_recursive.sql @@ -0,0 +1,36 @@ +-- Scenario: CTE chains, recursive trees and calendars +WITH RECURSIVE cnt(x) AS (SELECT 1 UNION ALL SELECT x + 1 FROM cnt WHERE x < 100) SELECT x FROM cnt; +WITH RECURSIVE tree(id, parent_id, name, depth) AS ( + SELECT id, parent_id, name, 0 FROM categories WHERE parent_id IS NULL + UNION ALL + SELECT c.id, c.parent_id, c.name, t.depth + 1 FROM categories c JOIN tree t ON c.parent_id = t.id +) +SELECT printf('%.*c%s', depth * 2, ' ', name) AS label FROM tree ORDER BY depth, name; +WITH RECURSIVE dates(d) AS ( + VALUES (date('2024-01-01')) + UNION ALL + SELECT date(d, '+1 day') FROM dates WHERE d < date('2024-01-31') +) +SELECT d, strftime('%w', d) AS dow FROM dates; +WITH RECURSIVE path(id, trail) AS ( + SELECT id, name FROM categories WHERE parent_id IS NULL + UNION + SELECT c.id, p.trail || ' > ' || c.name FROM categories c JOIN path p ON c.parent_id = p.id +) SELECT * FROM path ORDER BY trail LIMIT 50; +WITH a AS (SELECT 1 AS n), b AS (SELECT n + 1 AS n FROM a), c AS (SELECT n + 1 AS n FROM b), d AS (SELECT n + 1 AS n FROM c) SELECT * FROM d; +WITH monthly AS ( + SELECT strftime('%Y-%m', posted_at) AS ym, account_id, sum(amount) AS total FROM transactions GROUP BY ym, account_id +), ranked AS ( + SELECT ym, account_id, total, rank() OVER (PARTITION BY ym ORDER BY total DESC) AS rk FROM monthly +), top3 AS ( + SELECT * FROM ranked WHERE rk <= 3 +) +SELECT t.ym, a.name, t.total FROM top3 t JOIN accounts a ON a.id = t.account_id ORDER BY t.ym, t.rk; +WITH cte(a, b) AS (SELECT 1, 2) SELECT a + b FROM cte; +WITH cte AS MATERIALIZED (SELECT * FROM transactions WHERE amount > 100) SELECT count(*) FROM cte; +WITH cte AS NOT MATERIALIZED (SELECT * FROM transactions) SELECT * FROM cte WHERE id = 1; +WITH del AS (SELECT id FROM notes WHERE pinned = 0) DELETE FROM notes WHERE id IN (SELECT id FROM del); +WITH src(k, v) AS (VALUES ('a', 1), ('b', 2)) INSERT INTO settings (key, value) SELECT k, v FROM src ON CONFLICT (key) DO UPDATE SET value = excluded.value; +WITH t AS (SELECT account_id, max(posted_at) AS last FROM transactions GROUP BY account_id) UPDATE accounts SET archived = 1 WHERE id IN (SELECT account_id FROM t WHERE last < date('now', '-1 year')); +SELECT * FROM (SELECT * FROM (SELECT * FROM (SELECT id, amount FROM transactions WHERE amount > 0) WHERE amount < 1000) ORDER BY amount) LIMIT 5; +SELECT a.id FROM accounts a WHERE a.balance > (SELECT avg(balance) FROM accounts WHERE currency = a.currency) AND EXISTS (SELECT 1 FROM transactions t WHERE t.account_id = a.id AND t.amount > (SELECT avg(amount) FROM transactions)); diff --git a/testdata/realworld/sqlite/08_reporting.sql b/testdata/realworld/sqlite/08_reporting.sql new file mode 100644 index 00000000..174f4849 --- /dev/null +++ b/testdata/realworld/sqlite/08_reporting.sql @@ -0,0 +1,32 @@ +-- Scenario: BI / reporting queries over an orders schema +SELECT c.region, strftime('%Y', o.ordered_at) AS yr, count(DISTINCT o.id) AS orders, sum(oi.qty * oi.unit_price) AS revenue +FROM orders o +JOIN customers c ON c.id = o.customer_id +JOIN order_items oi ON oi.order_id = o.id +JOIN products p ON p.id = oi.product_id +LEFT JOIN refunds r ON r.order_id = o.id +WHERE o.status NOT IN ('cancelled', 'draft') AND r.id IS NULL +GROUP BY c.region, yr +HAVING revenue > 1000 +ORDER BY yr DESC, revenue DESC +LIMIT 100; +SELECT p.category, p.name, sum(oi.qty) AS units FROM products p LEFT JOIN order_items oi ON oi.product_id = p.id GROUP BY p.id ORDER BY units DESC NULLS LAST LIMIT 10; +SELECT date(ordered_at) AS day, count(*) FROM orders WHERE ordered_at >= date('now', '-30 days') GROUP BY day ORDER BY day; +SELECT customer_id, min(ordered_at) AS first_order, max(ordered_at) AS last_order, julianday(max(ordered_at)) - julianday(min(ordered_at)) AS span_days FROM orders GROUP BY customer_id; +SELECT CASE WHEN total < 50 THEN 'small' WHEN total < 500 THEN 'medium' ELSE 'large' END AS bucket, count(*) AS n, round(avg(total), 2) AS avg_total FROM orders GROUP BY bucket; +SELECT c.id, c.name, (SELECT count(*) FROM orders o WHERE o.customer_id = c.id) AS n_orders, (SELECT max(total) FROM orders o WHERE o.customer_id = c.id) AS biggest FROM customers c WHERE c.created_at BETWEEN '2024-01-01' AND '2024-12-31'; +SELECT product_id FROM order_items GROUP BY product_id HAVING sum(qty) > (SELECT avg(q) FROM (SELECT sum(qty) AS q FROM order_items GROUP BY product_id)); +SELECT a.customer_id, b.customer_id FROM orders a JOIN orders b ON a.id < b.id AND a.customer_id = b.customer_id AND abs(julianday(a.ordered_at) - julianday(b.ordered_at)) < 1; +SELECT o.id FROM orders o WHERE o.id IN (SELECT order_id FROM order_items WHERE product_id IN (SELECT id FROM products WHERE category = 'books')); +SELECT customer_id FROM orders WHERE status = 'paid' EXCEPT SELECT customer_id FROM refunds; +SELECT customer_id FROM orders WHERE total > 100 INTERSECT SELECT customer_id FROM orders WHERE status = 'paid' ORDER BY 1; +SELECT id, name FROM customers UNION ALL SELECT id, name FROM archived_customers ORDER BY name LIMIT 10 OFFSET 5; +SELECT 'orders' AS t, count(*) FROM orders UNION ALL SELECT 'customers', count(*) FROM customers UNION ALL SELECT 'products', count(*) FROM products; +SELECT coalesce(sum(total), 0) AS rev, count(*) FILTER (WHERE status = 'paid') AS paid FROM orders WHERE ordered_at >= :from AND ordered_at < :to; +SELECT * FROM orders WHERE customer_id = $cid AND status = @status; +SELECT * FROM orders WHERE id = ?1 OR id = ?2; +SELECT p.name, round(100.0 * sum(oi.qty * oi.unit_price) / (SELECT sum(qty * unit_price) FROM order_items), 2) AS share FROM products p JOIN order_items oi ON oi.product_id = p.id GROUP BY p.id ORDER BY share DESC; +SELECT cast(strftime('%m', ordered_at) AS INTEGER) AS m, sum(total) FROM orders GROUP BY m; +SELECT o.* FROM orders AS o INNER JOIN (SELECT customer_id, max(ordered_at) AS mx FROM orders GROUP BY customer_id) AS latest ON latest.customer_id = o.customer_id AND latest.mx = o.ordered_at; +SELECT count(*) AS "Total Orders", sum(total) AS "Total Revenue" FROM orders; +SELECT t1.a AS "x.y" FROM t t1 WHERE t1.a <> ''; diff --git a/testdata/realworld/sqlite/09_etl_admin.sql b/testdata/realworld/sqlite/09_etl_admin.sql new file mode 100644 index 00000000..877940c1 --- /dev/null +++ b/testdata/realworld/sqlite/09_etl_admin.sql @@ -0,0 +1,42 @@ +-- Scenario: ETL, bulk loads, admin and maintenance statements +CREATE TABLE IF NOT EXISTS staging_orders (raw_id TEXT, raw_total TEXT, raw_date TEXT); +CREATE TABLE IF NOT EXISTS etl_log (id INTEGER PRIMARY KEY AUTOINCREMENT, step TEXT, rows_affected INTEGER, ran_at TEXT DEFAULT CURRENT_TIMESTAMP); +INSERT INTO orders (id, total, ordered_at) SELECT CAST(raw_id AS INTEGER), CAST(raw_total AS REAL), date(raw_date) FROM staging_orders WHERE raw_id GLOB '[0-9]*'; +INSERT INTO etl_log (step, rows_affected) VALUES ('load_orders', changes()); +INSERT INTO orders SELECT * FROM orders_backup WHERE id NOT IN (SELECT id FROM orders); +DELETE FROM staging_orders; +DELETE FROM orders WHERE rowid NOT IN (SELECT min(rowid) FROM orders GROUP BY id); +UPDATE orders SET total = round(total, 2), status = lower(trim(status)); +UPDATE orders SET (total, status) = (SELECT sum(qty * unit_price), 'recalc' FROM order_items WHERE order_id = orders.id) WHERE id = 1; +UPDATE orders SET total = t.sum FROM (SELECT order_id, sum(qty * unit_price) AS sum FROM order_items GROUP BY order_id) AS t WHERE orders.id = t.order_id; +UPDATE orders AS o SET status = 'vip' FROM customers AS c WHERE c.id = o.customer_id AND c.tier = 'gold'; +DROP TABLE IF EXISTS staging_orders; +ALTER TABLE orders ADD COLUMN source TEXT DEFAULT 'import'; +CREATE TABLE orders_new (id INTEGER PRIMARY KEY, total REAL NOT NULL, ordered_at TEXT); +INSERT INTO orders_new SELECT id, total, ordered_at FROM orders; +DROP TABLE orders; +ALTER TABLE orders_new RENAME TO orders; +ATTACH DATABASE 'archive.db' AS archive; +ATTACH 'other.db' AS other; +DETACH DATABASE archive; +INSERT INTO archive.orders SELECT * FROM main.orders WHERE ordered_at < '2020-01-01'; +SELECT * FROM archive.orders UNION ALL SELECT * FROM main.orders; +PRAGMA foreign_keys = ON; +PRAGMA journal_mode = WAL; +PRAGMA table_info(orders); +PRAGMA user_version; +PRAGMA user_version = 5; +PRAGMA integrity_check; +PRAGMA main.synchronous = NORMAL; +VACUUM INTO 'backup.db'; +VACUUM main; +EXPLAIN SELECT * FROM orders WHERE id = 1; +EXPLAIN QUERY PLAN SELECT * FROM orders o JOIN customers c ON c.id = o.customer_id; +REINDEX; +ANALYZE main; +END TRANSACTION; +BEGIN DEFERRED TRANSACTION; +BEGIN EXCLUSIVE; +COMMIT TRANSACTION; +ROLLBACK TO SAVEPOINT sp1; +ROLLBACK TRANSACTION TO sp2; diff --git a/testdata/realworld/sqlite/10_mobile_offline_sync.sql b/testdata/realworld/sqlite/10_mobile_offline_sync.sql new file mode 100644 index 00000000..ce838840 --- /dev/null +++ b/testdata/realworld/sqlite/10_mobile_offline_sync.sql @@ -0,0 +1,46 @@ +-- Scenario: mobile offline-first sync, outbox pattern and FTS +CREATE TABLE IF NOT EXISTS outbox ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + entity TEXT NOT NULL, + entity_id TEXT NOT NULL, + op TEXT NOT NULL CHECK (op IN ('insert', 'update', 'delete')), + payload TEXT, + attempts INTEGER NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL DEFAULT (unixepoch()), + UNIQUE (entity, entity_id, op) +); +CREATE TABLE IF NOT EXISTS messages ( + id TEXT PRIMARY KEY NOT NULL, + thread_id TEXT NOT NULL, + sender TEXT NOT NULL, + body TEXT, + sent_at INTEGER NOT NULL, + read_at INTEGER, + deleted INTEGER NOT NULL DEFAULT 0 CHECK (deleted IN (0, 1)) +); +CREATE INDEX IF NOT EXISTS idx_messages_thread ON messages (thread_id, sent_at DESC) WHERE deleted = 0; +CREATE INDEX IF NOT EXISTS idx_messages_unread ON messages (thread_id) WHERE read_at IS NULL AND deleted = 0; +CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(body, content='messages', content_rowid='rowid'); +CREATE TRIGGER IF NOT EXISTS messages_ai AFTER INSERT ON messages BEGIN + INSERT INTO messages_fts (rowid, body) VALUES (new.rowid, new.body); +END; +CREATE TRIGGER IF NOT EXISTS messages_ad AFTER DELETE ON messages BEGIN + INSERT INTO messages_fts (messages_fts, rowid, body) VALUES ('delete', old.rowid, old.body); +END; +INSERT INTO outbox (entity, entity_id, op, payload) VALUES ('message', ?, 'insert', ?) ON CONFLICT (entity, entity_id, op) DO UPDATE SET payload = excluded.payload, attempts = 0; +SELECT * FROM outbox ORDER BY id LIMIT 50; +UPDATE outbox SET attempts = attempts + 1 WHERE id IN (?, ?, ?); +DELETE FROM outbox WHERE id <= ? AND attempts < 5; +SELECT thread_id, count(*) AS unread FROM messages WHERE read_at IS NULL AND deleted = 0 GROUP BY thread_id; +SELECT m.* FROM messages m WHERE m.thread_id = ? AND m.deleted = 0 AND m.sent_at < ? ORDER BY m.sent_at DESC LIMIT 30; +SELECT m.id, snippet(messages_fts, 0, '', '', '...', 8) FROM messages_fts JOIN messages m ON m.rowid = messages_fts.rowid WHERE messages_fts MATCH ? ORDER BY rank LIMIT 20; +SELECT * FROM messages_fts WHERE messages_fts MATCH 'hello AND world'; +UPDATE messages SET read_at = unixepoch() WHERE thread_id = ? AND read_at IS NULL; +INSERT OR REPLACE INTO messages (id, thread_id, sender, body, sent_at) VALUES (?, ?, ?, ?, ?); +SELECT t.thread_id, t.last_at, m.body FROM (SELECT thread_id, max(sent_at) AS last_at FROM messages WHERE deleted = 0 GROUP BY thread_id) t JOIN messages m ON m.thread_id = t.thread_id AND m.sent_at = t.last_at ORDER BY t.last_at DESC; +CREATE TABLE IF NOT EXISTS kv (k TEXT PRIMARY KEY, v BLOB) WITHOUT ROWID; +CREATE TABLE IF NOT EXISTS sync_state ( + entity TEXT PRIMARY KEY, + cursor TEXT, + last_sync INTEGER +) STRICT; diff --git a/testdata/realworld/sqlite/11_deep_nesting.sql b/testdata/realworld/sqlite/11_deep_nesting.sql new file mode 100644 index 00000000..04f818e7 --- /dev/null +++ b/testdata/realworld/sqlite/11_deep_nesting.sql @@ -0,0 +1,35 @@ +-- Scenario: stress with deep nesting and long expressions +SELECT ((((1 + 2) * 3) - 4) / 5) AS x; +SELECT a FROM t WHERE ((a > 1 AND b < 2) OR (c = 3 AND (d = 4 OR (e = 5 AND f = 6)))) AND NOT (g = 7 OR h = 8); +SELECT 1 + 2 * 3 - 4 / 2 % 3; +SELECT -(-1), - -1, +-+1; +SELECT a FROM t WHERE a = 1 OR b = 2 AND c = 3; +SELECT a FROM t WHERE NOT a = 1 AND b = 2; +SELECT a || 'x' = b || 'y' FROM t; +SELECT a IN (1, 2) = 1, b BETWEEN 1 AND 2 = 1 FROM t; +SELECT CASE WHEN a THEN CASE WHEN b THEN 1 ELSE 2 END ELSE CASE WHEN c THEN 3 ELSE 4 END END FROM t; +SELECT coalesce(nullif(trim(lower(replace(substr(name, 1, 10), ' ', '_'))), ''), 'unknown') FROM t; +SELECT (SELECT (SELECT (SELECT 1))); +SELECT * FROM t WHERE a IN (SELECT b FROM u WHERE c IN (SELECT d FROM v WHERE e IN (SELECT f FROM w))); +SELECT * FROM a JOIN b ON a.id = b.a_id JOIN c ON c.id = b.c_id JOIN d ON d.id = c.d_id LEFT JOIN e ON e.id = d.e_id LEFT JOIN f ON f.id = e.f_id JOIN g USING (id) WHERE a.x > 0; +SELECT * FROM (a JOIN b ON a.id = b.id) JOIN c ON c.id = a.id; +SELECT * FROM a LEFT JOIN (b JOIN c ON b.id = c.id) ON a.id = b.id; +SELECT x FROM (SELECT x FROM (SELECT x FROM (SELECT 1 AS x) AS t1) AS t2) AS t3; +SELECT * FROM t WHERE a = (SELECT max(a) FROM t WHERE b < (SELECT avg(b) FROM t)); +SELECT a, b FROM t1 UNION SELECT a, b FROM (SELECT a, b FROM t2 UNION ALL SELECT a, b FROM t3) ORDER BY a; +SELECT count(*), sum(CASE WHEN status = 'a' THEN 1 ELSE 0 END), sum(CASE WHEN status = 'b' THEN 1 ELSE 0 END) FROM t GROUP BY (a), (b); +SELECT * FROM t WHERE a = 'a''b' AND b = "c" AND c LIKE '%\_%' ESCAPE '\'; +SELECT max(a, b) AS m, min(a, b) AS n FROM t WHERE a IS NOT NULL; +SELECT a.*, b.* FROM a, b; +SELECT t.* FROM t; +SELECT ABS(x) AS Abs, Count(*) FROM T GROUP BY ABS(x); +SELECT a /* inline */ FROM t -- trailing +WHERE b = 1; +SELECT CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP; +SELECT TRUE, FALSE, NULL; +SELECT * FROM t WHERE flag = TRUE; +SELECT a FROM t WHERE (a) = (1); +SELECT 5 BETWEEN 1 AND 10 AND 6 NOT IN (1, 2); +SELECT a FROM t WHERE b = x'00' OR c = X'ff'; +SELECT 9223372036854775807, -9223372036854775808, 0.5, .5, 5., 1E-3; +SELECT a FROM t EXCEPT SELECT a FROM u UNION SELECT a FROM v; diff --git a/testdata/realworld/sqlite/12_orm_generated.sql b/testdata/realworld/sqlite/12_orm_generated.sql new file mode 100644 index 00000000..2ed6267a --- /dev/null +++ b/testdata/realworld/sqlite/12_orm_generated.sql @@ -0,0 +1,21 @@ +-- Scenario: typical ORM (Django/Rails/SQLAlchemy/Prisma-like) generated SQL against SQLite +SELECT "auth_user"."id", "auth_user"."password", "auth_user"."last_login", "auth_user"."username" FROM "auth_user" WHERE "auth_user"."username" = 'bob' LIMIT 21; +SELECT COUNT(*) AS "__count" FROM "blog_post" WHERE "blog_post"."published" = 1; +SELECT "blog_post"."id", "blog_post"."title" FROM "blog_post" INNER JOIN "auth_user" ON ("blog_post"."author_id" = "auth_user"."id") WHERE ("auth_user"."is_staff" AND NOT ("blog_post"."draft")) ORDER BY "blog_post"."created" DESC LIMIT 10; +INSERT INTO "blog_post" ("title", "body", "author_id", "created") VALUES ('t', 'b', 1, '2024-01-01 00:00:00') RETURNING "id"; +UPDATE "blog_post" SET "title" = 'new', "modified" = '2024-02-02 00:00:00' WHERE "blog_post"."id" = 1; +DELETE FROM "blog_post" WHERE "blog_post"."id" IN (1, 2, 3); +CREATE TABLE "django_session" ("session_key" varchar(40) NOT NULL PRIMARY KEY, "session_data" text NOT NULL, "expire_date" datetime NOT NULL); +CREATE INDEX "django_session_expire_date_a5c62663" ON "django_session" ("expire_date"); +CREATE TABLE "blog_post" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "title" varchar(200) NOT NULL, "author_id" integer NOT NULL REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED); +CREATE TABLE "new__blog_post" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "title" varchar(100) NOT NULL); +CREATE TABLE "t_dec" ("price" decimal NOT NULL, "n" bigint NOT NULL, "f" real NULL, "b" bool NOT NULL, "d" date NULL, "ts" datetime NOT NULL, "bl" blob NULL, "ch" char(3) NOT NULL, "sm" smallint unsigned NOT NULL CHECK ("sm" >= 0)); +CREATE TABLE knex_migrations (id integer not null primary key autoincrement, name varchar(255), batch integer, migration_time datetime); +CREATE TABLE `users` (`id` integer not null primary key autoincrement, `name` varchar(255)); +SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?; +SELECT "t0"."id", "t0"."name", "t1"."id" AS "t1_id" FROM "users" AS "t0" LEFT JOIN "profiles" AS "t1" ON "t1"."user_id" = "t0"."id" WHERE "t0"."id" IN (?, ?, ?) ORDER BY "t0"."id" ASC LIMIT ? OFFSET ?; +SELECT `User`.`id` AS `User.id`, `User`.`name` FROM `Users` AS `User` WHERE (`User`.`deletedAt` IS NULL AND `User`.`id` = 1); +INSERT INTO `Users` (`id`, `name`, `createdAt`) VALUES (NULL, ?1, ?2); +SELECT count(*) AS "count" FROM (SELECT 1 FROM "orders" GROUP BY "customer_id") AS "sub"; +SELECT * FROM "users" WHERE "id" = ? AND "deleted_at" IS NULL; +PRAGMA foreign_key_check; diff --git a/testdata/realworld/sqlserver/01_app_crud.sql b/testdata/realworld/sqlserver/01_app_crud.sql new file mode 100644 index 00000000..cbe4229e --- /dev/null +++ b/testdata/realworld/sqlserver/01_app_crud.sql @@ -0,0 +1,27 @@ +-- ORM-style CRUD for an order management application (Dapper / EF Core generated shapes) +SELECT [Id], [Name], [Email], [CreatedAt] FROM [dbo].[Customers] WHERE [Id] = 42; +SELECT TOP (1) c.[Id], c.[Name] FROM [dbo].[Customers] AS c WHERE c.[Email] = N'jose@example.com' ORDER BY c.[CreatedAt] DESC; +SELECT [Id], [Name] FROM dbo.Customers WITH (NOLOCK) WHERE IsActive = 1 AND Country IN (N'US', N'CA', N'MX'); +INSERT INTO [dbo].[Customers] ([Name], [Email], [CreatedAt], [IsActive]) VALUES (N'Zoë Müller', N'zoe@example.com', SYSUTCDATETIME(), 1); +INSERT INTO dbo.Customers (Name, Email) VALUES (N'A', N'a@x.com'), (N'B', N'b@x.com'), (N'C', N'c@x.com'); +INSERT INTO [dbo].[Orders] ([CustomerId], [Total], [Status]) OUTPUT INSERTED.[Id] VALUES (42, 199.99, N'New'); +INSERT INTO dbo.AuditLog (EventType, Payload, CreatedAt) OUTPUT INSERTED.Id, INSERTED.CreatedAt VALUES (N'login', N'{"user":1}', GETUTCDATE()); +UPDATE [dbo].[Customers] SET [Name] = N'Updated', [ModifiedAt] = GETUTCDATE() WHERE [Id] = 42; +UPDATE dbo.Orders SET Status = N'Shipped', ShippedAt = SYSDATETIME() OUTPUT DELETED.Status AS OldStatus, INSERTED.Status AS NewStatus WHERE Id = 1001; +UPDATE o SET o.Total = o.Total * 1.1 FROM dbo.Orders AS o INNER JOIN dbo.Customers AS c ON c.Id = o.CustomerId WHERE c.Country = N'DE'; +UPDATE TOP (500) dbo.Queue SET Status = 1 WHERE Status = 0; +DELETE FROM [dbo].[Orders] WHERE [Id] = 1001 AND [Status] = N'Cancelled'; +DELETE TOP (1000) FROM dbo.AuditLog WHERE CreatedAt < DATEADD(DAY, -90, GETUTCDATE()); +DELETE FROM dbo.Sessions OUTPUT DELETED.* WHERE ExpiresAt < GETUTCDATE(); +DELETE o FROM dbo.Orders o LEFT JOIN dbo.Customers c ON c.Id = o.CustomerId WHERE c.Id IS NULL; +SELECT COUNT(*) FROM dbo.Orders WHERE CustomerId = 42; +SELECT CASE WHEN EXISTS (SELECT 1 FROM dbo.Customers WHERE Email = N'x@y.com') THEN 1 ELSE 0 END; +SELECT c.Id, c.Name, (SELECT COUNT(*) FROM dbo.Orders o WHERE o.CustomerId = c.Id) AS OrderCount FROM dbo.Customers c; +SELECT * FROM dbo.Products WHERE Name LIKE N'%widget%' AND Price BETWEEN 10 AND 100 AND Discontinued IS NULL; +SELECT p.Id, p.Name FROM dbo.Products p WHERE p.CategoryId IN (SELECT Id FROM dbo.Categories WHERE ParentId = 5) ORDER BY p.Name OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY; +SELECT Id, Name FROM dbo.Products ORDER BY Id OFFSET @Skip ROWS FETCH NEXT @Take ROWS ONLY; +SELECT SCOPE_IDENTITY(); +SELECT @@ROWCOUNT; +SELECT @@IDENTITY AS LastId; +SELECT TOP 10 * FROM dbo.Orders ORDER BY CreatedAt DESC; +SELECT DISTINCT TOP (5) Country FROM dbo.Customers ORDER BY Country; diff --git a/testdata/realworld/sqlserver/02_reporting.sql b/testdata/realworld/sqlserver/02_reporting.sql new file mode 100644 index 00000000..a1ac7f61 --- /dev/null +++ b/testdata/realworld/sqlserver/02_reporting.sql @@ -0,0 +1,27 @@ +-- Dashboards and ERP style reporting queries +SELECT YEAR(o.OrderDate) AS Yr, MONTH(o.OrderDate) AS Mo, SUM(od.Quantity * od.UnitPrice) AS Revenue FROM Sales.Orders o JOIN Sales.OrderDetails od ON od.OrderId = o.OrderId GROUP BY YEAR(o.OrderDate), MONTH(o.OrderDate) ORDER BY Yr, Mo; +SELECT c.CustomerName, COUNT(DISTINCT o.OrderId) AS Orders, SUM(od.Quantity * od.UnitPrice * (1 - od.Discount)) AS NetRevenue FROM Sales.Customers c LEFT JOIN Sales.Orders o ON o.CustomerId = c.CustomerId LEFT JOIN Sales.OrderDetails od ON od.OrderId = o.OrderId GROUP BY c.CustomerName HAVING SUM(od.Quantity * od.UnitPrice) > 1000 ORDER BY NetRevenue DESC; +SELECT TOP (10) PERCENT p.ProductName, p.ListPrice FROM Production.Product p ORDER BY p.ListPrice DESC; +SELECT TOP (3) WITH TIES e.Name, e.Salary FROM HR.Employees e ORDER BY e.Salary DESC; +SELECT TOP 5 PERCENT WITH TIES Name, Score FROM dbo.Players ORDER BY Score DESC; +SELECT Region, SUM(Amount) AS Total FROM dbo.Sales GROUP BY ROLLUP (Region); +SELECT Region, Product, SUM(Amount) AS Total FROM dbo.Sales GROUP BY CUBE (Region, Product); +SELECT Region, Product, SUM(Amount) AS Total, GROUPING(Region) AS gr FROM dbo.Sales GROUP BY GROUPING SETS ((Region, Product), (Region), ()); +SELECT Region, SUM(Amount) FROM dbo.Sales GROUP BY Region WITH ROLLUP; +SELECT d.DepartmentName, STRING_AGG(e.LastName, N', ') WITHIN GROUP (ORDER BY e.LastName) AS Employees FROM HR.Departments d JOIN HR.Employees e ON e.DepartmentId = d.DepartmentId GROUP BY d.DepartmentName; +SELECT STRING_AGG(CAST(Id AS NVARCHAR(20)), ',') FROM dbo.Orders WHERE Status = N'Open'; +SELECT CONVERT(VARCHAR(10), OrderDate, 120) AS OrderDay, COUNT(*) AS Cnt FROM dbo.Orders GROUP BY CONVERT(VARCHAR(10), OrderDate, 120); +SELECT FORMAT(OrderDate, 'yyyy-MM') AS Period, SUM(Total) AS Total FROM dbo.Orders GROUP BY FORMAT(OrderDate, 'yyyy-MM'); +SELECT DATEDIFF(DAY, OrderDate, ShippedDate) AS DaysToShip, AVG(Total) FROM dbo.Orders WHERE ShippedDate IS NOT NULL GROUP BY DATEDIFF(DAY, OrderDate, ShippedDate); +SELECT DATEADD(MONTH, DATEDIFF(MONTH, 0, OrderDate), 0) AS MonthStart, COUNT(*) FROM dbo.Orders GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, OrderDate), 0); +SELECT EOMONTH(GETDATE()) AS MonthEnd, EOMONTH(GETDATE(), -1) AS PrevMonthEnd; +SELECT DATEPART(QUARTER, OrderDate) AS Q, DATENAME(MONTH, OrderDate) AS MonthName FROM dbo.Orders; +SELECT IIF(Total > 1000, N'Large', N'Small') AS Size, COUNT(*) FROM dbo.Orders GROUP BY IIF(Total > 1000, N'Large', N'Small'); +SELECT ISNULL(Region, N'Unknown') AS Region, COALESCE(Manager, Supervisor, N'None') AS Boss, NULLIF(Qty, 0) AS Q FROM dbo.Sales; +SELECT TRY_CAST(Amount AS DECIMAL(18, 2)) AS Amt, TRY_CONVERT(DATE, DateText, 103) AS D, TRY_PARSE(N'2024-01-01' AS DATE) AS P FROM dbo.Import; +SELECT CHOOSE(MONTH(GETDATE()), N'Jan', N'Feb', N'Mar', N'Apr', N'May', N'Jun', N'Jul', N'Aug', N'Sep', N'Oct', N'Nov', N'Dec'); +SELECT * FROM (SELECT Region, Quarter, Amount FROM dbo.Sales) AS src PIVOT (SUM(Amount) FOR Quarter IN ([Q1], [Q2], [Q3], [Q4])) AS pvt; +SELECT CustomerId, Metric, Value FROM dbo.CustomerStats UNPIVOT (Value FOR Metric IN (Orders, Returns, Visits)) AS u; +SELECT s.Name FROM sys.schemas s WHERE s.name <> N'sys'; +SELECT SUM(CASE WHEN Status = N'Open' THEN 1 ELSE 0 END) AS OpenCnt, SUM(CASE WHEN Status = N'Closed' THEN 1 ELSE 0 END) AS ClosedCnt FROM dbo.Tickets; +SELECT a.AccountId, a.Balance, b.Balance AS Prior FROM dbo.Accounts a INNER JOIN dbo.AccountSnapshots b ON a.AccountId = b.AccountId AND b.SnapshotDate = CAST(GETDATE() - 1 AS DATE) WHERE a.Balance <> b.Balance; diff --git a/testdata/realworld/sqlserver/03_ddl_migrations.sql b/testdata/realworld/sqlserver/03_ddl_migrations.sql new file mode 100644 index 00000000..8bbc4d26 --- /dev/null +++ b/testdata/realworld/sqlserver/03_ddl_migrations.sql @@ -0,0 +1,33 @@ +-- Schema migrations and DDL as generated by EF Core / Flyway / hand written scripts +CREATE TABLE [dbo].[Customers] ([Id] INT IDENTITY(1,1) NOT NULL, [Name] NVARCHAR(200) NOT NULL, [Email] NVARCHAR(320) NULL, [CreatedAt] DATETIME2(7) NOT NULL CONSTRAINT [DF_Customers_CreatedAt] DEFAULT (SYSUTCDATETIME()), [IsActive] BIT NOT NULL DEFAULT 1, CONSTRAINT [PK_Customers] PRIMARY KEY CLUSTERED ([Id] ASC)); +CREATE TABLE dbo.Orders (Id BIGINT IDENTITY(1, 1) PRIMARY KEY, CustomerId INT NOT NULL, Total DECIMAL(18, 2) NOT NULL DEFAULT 0, Status NVARCHAR(20) NOT NULL, CreatedAt DATETIME2 NOT NULL DEFAULT GETUTCDATE(), CONSTRAINT FK_Orders_Customers FOREIGN KEY (CustomerId) REFERENCES dbo.Customers (Id) ON DELETE CASCADE); +CREATE TABLE dbo.Products (Id INT IDENTITY PRIMARY KEY, Sku VARCHAR(50) NOT NULL UNIQUE, Name NVARCHAR(MAX) NULL, Price MONEY NOT NULL, RowVer ROWVERSION, Guid UNIQUEIDENTIFIER NOT NULL DEFAULT NEWID(), CHECK (Price >= 0)); +CREATE TABLE #staging (Id INT, Payload NVARCHAR(MAX)); +CREATE TABLE dbo.Computed (A INT, B INT, Total AS (A + B), Doubled AS A * 2 PERSISTED); +ALTER TABLE [dbo].[Customers] ADD [Phone] NVARCHAR(30) NULL; +ALTER TABLE dbo.Customers ADD Region VARCHAR(10) NULL, Tier TINYINT NOT NULL CONSTRAINT DF_Cust_Tier DEFAULT 0; +ALTER TABLE dbo.Customers ALTER COLUMN Name NVARCHAR(400) NOT NULL; +ALTER TABLE dbo.Customers DROP COLUMN Phone; +ALTER TABLE dbo.Customers DROP CONSTRAINT DF_Cust_Tier; +ALTER TABLE dbo.Orders ADD CONSTRAINT CK_Orders_Total CHECK (Total >= 0); +ALTER TABLE dbo.Orders WITH CHECK ADD CONSTRAINT FK_Orders_Cust FOREIGN KEY (CustomerId) REFERENCES dbo.Customers (Id); +ALTER TABLE dbo.Orders NOCHECK CONSTRAINT FK_Orders_Cust; +ALTER TABLE dbo.Orders ADD CONSTRAINT UQ_Orders_Ref UNIQUE NONCLUSTERED (CustomerId, CreatedAt); +CREATE INDEX IX_Orders_CustomerId ON dbo.Orders (CustomerId); +CREATE NONCLUSTERED INDEX [IX_Orders_Status] ON [dbo].[Orders] ([Status] ASC) INCLUDE ([Total], [CreatedAt]) WHERE [Status] <> N'Closed' WITH (FILLFACTOR = 90, ONLINE = ON); +CREATE UNIQUE CLUSTERED INDEX UX_Products_Sku ON dbo.Products (Sku); +CREATE INDEX IX_Cust_Email ON dbo.Customers (Email DESC, Name); +DROP INDEX IX_Orders_CustomerId ON dbo.Orders; +DROP TABLE dbo.OldStuff; +DROP TABLE IF EXISTS dbo.Temp1; +DROP TABLE IF EXISTS #staging; +CREATE VIEW dbo.vw_ActiveCustomers AS SELECT Id, Name, Email FROM dbo.Customers WHERE IsActive = 1; +CREATE OR ALTER VIEW dbo.vw_OrderTotals AS SELECT CustomerId, SUM(Total) AS Total FROM dbo.Orders GROUP BY CustomerId; +DROP VIEW IF EXISTS dbo.vw_Old; +CREATE SCHEMA Reporting; +CREATE SEQUENCE dbo.OrderNumbers AS BIGINT START WITH 1000 INCREMENT BY 1; +SELECT NEXT VALUE FOR dbo.OrderNumbers; +TRUNCATE TABLE dbo.Staging; +CREATE TYPE dbo.IdList AS TABLE (Id INT NOT NULL PRIMARY KEY); +SELECT * INTO #recent FROM dbo.Orders WHERE CreatedAt > DATEADD(DAY, -7, GETDATE()); +SELECT Id, Name INTO dbo.CustomersBackup FROM dbo.Customers; diff --git a/testdata/realworld/sqlserver/04_window_analytics.sql b/testdata/realworld/sqlserver/04_window_analytics.sql new file mode 100644 index 00000000..a2bfc94e --- /dev/null +++ b/testdata/realworld/sqlserver/04_window_analytics.sql @@ -0,0 +1,18 @@ +-- Window functions and analytics +SELECT OrderId, CustomerId, Total, ROW_NUMBER() OVER (PARTITION BY CustomerId ORDER BY OrderDate DESC) AS rn FROM dbo.Orders; +SELECT Name, Dept, Salary, RANK() OVER (PARTITION BY Dept ORDER BY Salary DESC) AS rk, DENSE_RANK() OVER (ORDER BY Salary DESC) AS drk, NTILE(4) OVER (ORDER BY Salary) AS quartile FROM HR.Employees; +SELECT OrderDate, Total, SUM(Total) OVER (ORDER BY OrderDate ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS RunningTotal FROM dbo.Orders; +SELECT OrderDate, Total, AVG(Total) OVER (ORDER BY OrderDate ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS MovAvg7 FROM dbo.Orders; +SELECT OrderDate, Total, LAG(Total, 1, 0) OVER (ORDER BY OrderDate) AS Prev, LEAD(Total) OVER (ORDER BY OrderDate) AS NextTotal FROM dbo.Orders; +SELECT Dept, Name, FIRST_VALUE(Name) OVER (PARTITION BY Dept ORDER BY Salary DESC) AS TopEarner, LAST_VALUE(Name) OVER (PARTITION BY Dept ORDER BY Salary DESC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS Lowest FROM HR.Employees; +SELECT Name, Salary, PERCENT_RANK() OVER (ORDER BY Salary) AS pr, CUME_DIST() OVER (ORDER BY Salary) AS cd FROM HR.Employees; +SELECT DISTINCT Dept, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY Salary) OVER (PARTITION BY Dept) AS MedianSalary FROM HR.Employees; +SELECT Id, COUNT(*) OVER () AS TotalRows, COUNT(*) OVER (PARTITION BY Status) AS StatusRows FROM dbo.Tickets; +SELECT * FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY Email ORDER BY CreatedAt) AS rn FROM dbo.Customers) d WHERE d.rn > 1; +SELECT SUM(Amount) OVER w AS S, AVG(Amount) OVER w AS A FROM dbo.Sales WINDOW w AS (PARTITION BY Region ORDER BY SaleDate); +WITH Ranked AS (SELECT ProductId, CategoryId, Price, ROW_NUMBER() OVER (PARTITION BY CategoryId ORDER BY Price DESC) AS rn FROM dbo.Products) SELECT ProductId, CategoryId, Price FROM Ranked WHERE rn <= 3; +WITH Daily AS (SELECT CAST(CreatedAt AS DATE) AS D, COUNT(*) AS C FROM dbo.Orders GROUP BY CAST(CreatedAt AS DATE)) SELECT D, C, SUM(C) OVER (ORDER BY D) AS Cumulative, C - LAG(C) OVER (ORDER BY D) AS Delta FROM Daily; +WITH gaps AS (SELECT Id, Id - ROW_NUMBER() OVER (ORDER BY Id) AS grp FROM dbo.Seq) SELECT MIN(Id) AS StartId, MAX(Id) AS EndId FROM gaps GROUP BY grp; +SELECT CustomerId, MAX(CASE WHEN rn = 1 THEN OrderId END) AS LastOrder FROM (SELECT CustomerId, OrderId, ROW_NUMBER() OVER (PARTITION BY CustomerId ORDER BY OrderDate DESC) AS rn FROM dbo.Orders) t GROUP BY CustomerId; +SELECT Region, Amount, Amount * 100.0 / SUM(Amount) OVER (PARTITION BY Region) AS PctOfRegion FROM dbo.Sales; +SELECT OrderId, Total, SUM(Total) OVER (PARTITION BY CustomerId ORDER BY OrderDate RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS CustRunning FROM dbo.Orders; diff --git a/testdata/realworld/sqlserver/05_dialect_specific.sql b/testdata/realworld/sqlserver/05_dialect_specific.sql new file mode 100644 index 00000000..ec0025d2 --- /dev/null +++ b/testdata/realworld/sqlserver/05_dialect_specific.sql @@ -0,0 +1,36 @@ +-- T-SQL specific syntax: APPLY, MERGE, OUTPUT, FOR JSON/XML, hints, temp tables, variables +SELECT c.Id, c.Name, t.OrderId, t.Total FROM dbo.Customers c CROSS APPLY (SELECT TOP (3) o.OrderId, o.Total FROM dbo.Orders o WHERE o.CustomerId = c.Id ORDER BY o.Total DESC) AS t; +SELECT c.Id, c.Name, t.OrderId FROM dbo.Customers c OUTER APPLY (SELECT TOP (1) o.OrderId FROM dbo.Orders o WHERE o.CustomerId = c.Id ORDER BY o.OrderDate DESC) t; +SELECT o.OrderId, j.value FROM dbo.Orders o CROSS APPLY STRING_SPLIT(o.Tags, N',') AS j; +SELECT value FROM STRING_SPLIT(N'a,b,c', N','); +SELECT Id, Name FROM dbo.Customers FOR JSON PATH; +SELECT Id, Name FROM dbo.Customers FOR JSON PATH, ROOT('customers'), INCLUDE_NULL_VALUES; +SELECT c.Id, c.Name, (SELECT o.OrderId, o.Total FROM dbo.Orders o WHERE o.CustomerId = c.Id FOR JSON PATH) AS Orders FROM dbo.Customers c FOR JSON PATH, WITHOUT_ARRAY_WRAPPER; +SELECT Id, Name FROM dbo.Customers FOR JSON AUTO; +SELECT Id, Name FROM dbo.Customers FOR XML PATH('Customer'), ROOT('Customers'); +SELECT STUFF((SELECT N',' + Name FROM dbo.Customers FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, N''); +SELECT Id, JSON_VALUE(Payload, '$.user.name') AS UserName, JSON_QUERY(Payload, '$.items') AS Items FROM dbo.Events WHERE ISJSON(Payload) = 1; +SELECT e.Id, j.* FROM dbo.Events e CROSS APPLY OPENJSON(e.Payload) WITH (Name NVARCHAR(100) '$.name', Qty INT '$.qty') AS j; +SELECT * FROM OPENJSON(@json) WITH (Id INT '$.id', Name NVARCHAR(50) '$.name'); +SELECT * FROM dbo.Orders WITH (NOLOCK) WHERE Status = N'Open'; +SELECT * FROM dbo.Orders o WITH (NOLOCK, INDEX(IX_Orders_Status)) JOIN dbo.Customers c WITH (NOLOCK) ON c.Id = o.CustomerId; +SELECT * FROM dbo.Orders WITH (UPDLOCK, ROWLOCK, READPAST) WHERE Status = 0; +SELECT * FROM dbo.Orders (NOLOCK); +SELECT o.Id FROM dbo.Orders o INNER HASH JOIN dbo.Customers c ON c.Id = o.CustomerId OPTION (MAXDOP 1, RECOMPILE); +SELECT * FROM dbo.Orders WHERE CustomerId = @CustomerId OPTION (OPTIMIZE FOR (@CustomerId = 42)); +SELECT * FROM dbo.Orders OPTION (RECOMPILE); +SELECT @Total = SUM(Total) FROM dbo.Orders WHERE CustomerId = @CustomerId; +SELECT @a = 1, @b = 2; +SELECT TOP (@n) * FROM dbo.Orders ORDER BY Id; +SELECT * FROM dbo.Orders WHERE CreatedAt >= @From AND CreatedAt < @To AND (@Status IS NULL OR Status = @Status); +SELECT * FROM #recent r JOIN ##globaltemp g ON g.Id = r.Id; +SELECT GETDATE() AS Now, SYSDATETIMEOFFSET() AS NowOffset, NEWID() AS G, @@VERSION AS Ver, DB_NAME() AS Db, SUSER_SNAME() AS Usr; +SELECT CAST(N'abc' AS NVARCHAR(10)) + N'def' AS Concatenated, LEN(N'abc') AS L, DATALENGTH(N'abc') AS DL, CONCAT(N'a', N'b', N'c') AS C2, CONCAT_WS(N'-', N'a', N'b') AS C3; +SELECT TRIM(Name) AS T, LTRIM(RTRIM(Name)) AS LR, REPLACE(Name, N' ', N'_') AS R, SUBSTRING(Name, 1, 3) AS S, CHARINDEX(N'@', Email) AS At, LEFT(Name, 2) AS L2, RIGHT(Name, 2) AS R2, UPPER(Name) AS U FROM dbo.Customers; +SELECT 0x1F AS HexVal, 1.5E3 AS Sci, $5.00 AS Money; +SELECT [Order Details].[Unit Price], [Order Details].[Quantity] FROM [Order Details]; +SELECT "Name" FROM "dbo"."Customers"; +SELECT * FROM [MyServer].[MyDb].[dbo].[Customers]; +SELECT * FROM MyDb.dbo.Customers; +SELECT * FROM MyDb..Customers; +SELECT a.Id FROM dbo.A a, dbo.B b WHERE a.Id = b.Id; diff --git a/testdata/realworld/sqlserver/06_merge_etl.sql b/testdata/realworld/sqlserver/06_merge_etl.sql new file mode 100644 index 00000000..4849fd0b --- /dev/null +++ b/testdata/realworld/sqlserver/06_merge_etl.sql @@ -0,0 +1,20 @@ +-- ETL upserts with MERGE and OUTPUT +MERGE dbo.Customers AS tgt USING dbo.CustomersStage AS src ON tgt.Id = src.Id WHEN MATCHED THEN UPDATE SET tgt.Name = src.Name, tgt.Email = src.Email WHEN NOT MATCHED BY TARGET THEN INSERT (Id, Name, Email) VALUES (src.Id, src.Name, src.Email); +MERGE INTO dbo.Inventory AS t USING (SELECT Sku, SUM(Qty) AS Qty FROM dbo.Receipts GROUP BY Sku) AS s ON t.Sku = s.Sku WHEN MATCHED THEN UPDATE SET t.OnHand = t.OnHand + s.Qty WHEN NOT MATCHED THEN INSERT (Sku, OnHand) VALUES (s.Sku, s.Qty); +MERGE dbo.Target AS t USING dbo.Source AS s ON t.Id = s.Id WHEN MATCHED AND s.IsDeleted = 1 THEN DELETE WHEN MATCHED THEN UPDATE SET t.Val = s.Val WHEN NOT MATCHED BY TARGET THEN INSERT (Id, Val) VALUES (s.Id, s.Val) WHEN NOT MATCHED BY SOURCE THEN DELETE; +MERGE dbo.Target AS t USING dbo.Source AS s ON t.Id = s.Id WHEN MATCHED THEN UPDATE SET t.Val = s.Val WHEN NOT MATCHED THEN INSERT (Id, Val) VALUES (s.Id, s.Val) OUTPUT $action, INSERTED.Id, DELETED.Val; +MERGE dbo.Target AS t USING (VALUES (1, N'a'), (2, N'b')) AS s (Id, Val) ON t.Id = s.Id WHEN MATCHED THEN UPDATE SET Val = s.Val WHEN NOT MATCHED THEN INSERT (Id, Val) VALUES (s.Id, s.Val); +MERGE dbo.Dim AS d USING dbo.Stg AS s ON d.BK = s.BK WHEN MATCHED AND (d.Name <> s.Name OR d.City <> s.City) THEN UPDATE SET d.Name = s.Name, d.City = s.City, d.UpdatedAt = GETUTCDATE() WHEN NOT MATCHED BY SOURCE AND d.IsCurrent = 1 THEN UPDATE SET d.IsCurrent = 0; +INSERT INTO dbo.OrdersArchive (Id, CustomerId, Total) SELECT Id, CustomerId, Total FROM dbo.Orders WHERE CreatedAt < DATEADD(YEAR, -2, GETDATE()); +INSERT INTO dbo.Orders (CustomerId, Total) SELECT CustomerId, SUM(Amount) FROM dbo.Payments GROUP BY CustomerId; +INSERT INTO dbo.Archive SELECT * FROM dbo.Live WHERE Id IN (SELECT Id FROM dbo.ToArchive); +INSERT INTO dbo.Defaults DEFAULT VALUES; +INSERT dbo.Customers (Name) VALUES (N'No INTO keyword'); +INSERT INTO dbo.Log (Msg) OUTPUT INSERTED.Id INTO @Ids (Id) VALUES (N'x'); +DELETE FROM dbo.Orders OUTPUT DELETED.Id, DELETED.Total INTO dbo.OrdersArchive (Id, Total) WHERE CreatedAt < '2020-01-01'; +UPDATE t SET t.Total = s.Total FROM dbo.Orders t JOIN (SELECT OrderId, SUM(Qty * Price) AS Total FROM dbo.Lines GROUP BY OrderId) s ON s.OrderId = t.Id; +UPDATE dbo.Counters SET @val = Value = Value + 1 WHERE Name = N'orders'; +UPDATE dbo.Products SET Price = CASE WHEN Price < 10 THEN Price * 1.2 WHEN Price < 100 THEN Price * 1.1 ELSE Price END; +UPDATE STATISTICS dbo.Orders; +WITH Dups AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY Email ORDER BY Id) AS rn FROM dbo.Customers) DELETE FROM Dups WHERE rn > 1; +WITH Src AS (SELECT TOP (100) * FROM dbo.Queue WITH (ROWLOCK, READPAST, UPDLOCK) ORDER BY Id) UPDATE Src SET Status = 1 OUTPUT INSERTED.Id, INSERTED.Payload; diff --git a/testdata/realworld/sqlserver/07_ctes_deep.sql b/testdata/realworld/sqlserver/07_ctes_deep.sql new file mode 100644 index 00000000..38182536 --- /dev/null +++ b/testdata/realworld/sqlserver/07_ctes_deep.sql @@ -0,0 +1,22 @@ +-- Complex CTE chains, recursion, deep nesting, many joins +WITH Org AS (SELECT EmployeeId, ManagerId, Name, 0 AS Lvl FROM HR.Employees WHERE ManagerId IS NULL UNION ALL SELECT e.EmployeeId, e.ManagerId, e.Name, o.Lvl + 1 FROM HR.Employees e JOIN Org o ON e.ManagerId = o.EmployeeId) SELECT * FROM Org ORDER BY Lvl OPTION (MAXRECURSION 100); +WITH Nums AS (SELECT 1 AS n UNION ALL SELECT n + 1 FROM Nums WHERE n < 1000) SELECT n FROM Nums OPTION (MAXRECURSION 0); +WITH Paths AS (SELECT Id, ParentId, CAST(Name AS NVARCHAR(MAX)) AS Path FROM dbo.Categories WHERE ParentId IS NULL UNION ALL SELECT c.Id, c.ParentId, p.Path + N' > ' + c.Name FROM dbo.Categories c INNER JOIN Paths p ON c.ParentId = p.Id) SELECT Id, Path FROM Paths; +WITH a AS (SELECT CustomerId, SUM(Total) AS T FROM dbo.Orders GROUP BY CustomerId), b AS (SELECT CustomerId, COUNT(*) AS C FROM dbo.Orders GROUP BY CustomerId), c AS (SELECT a.CustomerId, a.T, b.C FROM a JOIN b ON a.CustomerId = b.CustomerId), d AS (SELECT *, T / NULLIF(C, 0) AS Avg FROM c) SELECT * FROM d WHERE Avg > 100; +WITH Sales2024 (Region, Total) AS (SELECT Region, SUM(Amount) FROM dbo.Sales WHERE YEAR(SaleDate) = 2024 GROUP BY Region), Sales2023 (Region, Total) AS (SELECT Region, SUM(Amount) FROM dbo.Sales WHERE YEAR(SaleDate) = 2023 GROUP BY Region) SELECT a.Region, a.Total AS T24, b.Total AS T23, (a.Total - b.Total) * 100.0 / NULLIF(b.Total, 0) AS GrowthPct FROM Sales2024 a FULL OUTER JOIN Sales2023 b ON a.Region = b.Region; +SELECT o.OrderId, c.CustomerName, e.LastName, p.ProductName, cat.CategoryName, s.CompanyName, sh.CompanyName AS Shipper, r.RegionDescription FROM Sales.Orders o JOIN Sales.Customers c ON c.CustomerId = o.CustomerId JOIN HR.Employees e ON e.EmployeeId = o.EmployeeId JOIN Sales.OrderDetails od ON od.OrderId = o.OrderId JOIN Production.Products p ON p.ProductId = od.ProductId JOIN Production.Categories cat ON cat.CategoryId = p.CategoryId JOIN Production.Suppliers s ON s.SupplierId = p.SupplierId LEFT JOIN Sales.Shippers sh ON sh.ShipperId = o.ShipperId LEFT JOIN Geo.Regions r ON r.RegionId = c.RegionId WHERE o.OrderDate >= '20240101' AND o.OrderDate < '20250101'; +SELECT * FROM (SELECT * FROM (SELECT * FROM (SELECT Id, Name FROM dbo.Customers WHERE IsActive = 1) a WHERE Id > 10) b WHERE Id < 1000) c; +SELECT Id FROM dbo.A WHERE Id IN (SELECT Id FROM dbo.B WHERE Val > (SELECT AVG(Val) FROM dbo.C WHERE Grp IN (SELECT Grp FROM dbo.D WHERE EXISTS (SELECT 1 FROM dbo.E WHERE E.Id = D.Id)))); +SELECT Id FROM dbo.A WHERE NOT EXISTS (SELECT 1 FROM dbo.B WHERE B.AId = A.Id) AND Val > ALL (SELECT Val FROM dbo.C) AND Val < ANY (SELECT Val FROM dbo.D); +SELECT Id, Name FROM dbo.A UNION SELECT Id, Name FROM dbo.B UNION ALL SELECT Id, Name FROM dbo.C EXCEPT SELECT Id, Name FROM dbo.D INTERSECT SELECT Id, Name FROM dbo.E; +(SELECT Id FROM dbo.A UNION SELECT Id FROM dbo.B) INTERSECT SELECT Id FROM dbo.C ORDER BY Id; +SELECT t.* FROM (VALUES (1, N'a'), (2, N'b'), (3, N'c')) AS t (Id, Name); +SELECT a.Id, b.Id FROM dbo.A a CROSS JOIN dbo.B b WHERE a.Id <> b.Id; +SELECT a.Id FROM dbo.A a RIGHT OUTER JOIN dbo.B b ON a.Id = b.Id LEFT OUTER JOIN dbo.C c ON c.Id = b.Id FULL JOIN dbo.D d ON d.Id = c.Id; +SELECT CASE WHEN a > 1 THEN CASE WHEN b > 2 THEN N'x' ELSE N'y' END WHEN a < 0 THEN N'z' ELSE N'w' END FROM dbo.T; +SELECT (a + b) * (c - d) / NULLIF(e % 3, 0) AS Calc, -a AS Neg, ~b AS BitNot, a & b AS BitAnd, a | b AS BitOr, a ^ b AS BitXor FROM dbo.T; +SELECT * FROM dbo.T WHERE (a = 1 OR b = 2) AND NOT (c = 3 OR d IS NOT NULL) AND e NOT BETWEEN 1 AND 5 AND f NOT IN (1, 2, 3) AND g NOT LIKE N'x%' ESCAPE N'\'; +SELECT 1 + 2 * 3 - 4 / 2 AS Prec; +SELECT Name FROM dbo.Customers c WHERE c.Id = (SELECT MAX(Id) FROM dbo.Customers) ORDER BY 1; +SELECT COUNT(DISTINCT CustomerId) AS UniqueCust, COUNT_BIG(*) AS Big, APPROX_COUNT_DISTINCT(CustomerId) AS Approx FROM dbo.Orders; +SELECT a.Id, SUM(b.Qty) AS Q FROM dbo.A a JOIN dbo.B b ON b.AId = a.Id GROUP BY a.Id HAVING COUNT(*) > 1 AND SUM(b.Qty) < 100 ORDER BY Q DESC, a.Id; diff --git a/testdata/realworld/sqlserver/08_procs_batches.sql b/testdata/realworld/sqlserver/08_procs_batches.sql new file mode 100644 index 00000000..a8e1ac32 --- /dev/null +++ b/testdata/realworld/sqlserver/08_procs_batches.sql @@ -0,0 +1,33 @@ +-- Stored procedure calls, variable handling, control-flow statements +EXEC dbo.GetCustomer @Id = 42; +EXEC dbo.usp_Report @From = '2024-01-01', @To = '2024-12-31', @Region = N'EMEA'; +EXECUTE sp_executesql N'SELECT * FROM dbo.Orders WHERE Id = @Id', N'@Id INT', @Id = 5; +EXEC sp_help N'dbo.Customers'; +EXEC sp_rename N'dbo.Old', N'New'; +DECLARE @i INT = 0; +DECLARE @name NVARCHAR(100), @total DECIMAL(18, 2) = 0; +DECLARE @t TABLE (Id INT PRIMARY KEY, Name NVARCHAR(50)); +SET @i = @i + 1; +SET @name = (SELECT TOP (1) Name FROM dbo.Customers ORDER BY Id); +SET NOCOUNT ON; +SET XACT_ABORT ON; +SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +SET IDENTITY_INSERT dbo.Customers ON; +USE [Northwind]; +BEGIN TRANSACTION; +COMMIT TRANSACTION; +ROLLBACK; +IF EXISTS (SELECT 1 FROM sys.tables WHERE name = N'Customers') DROP TABLE dbo.Customers; +IF @i > 0 SELECT 1 ELSE SELECT 0; +WHILE @i < 10 BEGIN SET @i += 1; END; +BEGIN TRY SELECT 1 / 0; END TRY BEGIN CATCH SELECT ERROR_NUMBER() AS Err, ERROR_MESSAGE() AS Msg; END CATCH; +RAISERROR (N'Something failed: %s', 16, 1, N'detail'); +THROW 50001, N'Custom error', 1; +PRINT N'Done'; +CREATE PROCEDURE dbo.GetOrders @CustomerId INT, @Status NVARCHAR(20) = NULL AS BEGIN SET NOCOUNT ON; SELECT * FROM dbo.Orders WHERE CustomerId = @CustomerId AND (@Status IS NULL OR Status = @Status); END; +CREATE OR ALTER PROCEDURE dbo.UpsertCustomer @Id INT, @Name NVARCHAR(200) AS BEGIN UPDATE dbo.Customers SET Name = @Name WHERE Id = @Id; IF @@ROWCOUNT = 0 INSERT INTO dbo.Customers (Id, Name) VALUES (@Id, @Name); END; +CREATE FUNCTION dbo.fn_Tax (@amount DECIMAL(18, 2)) RETURNS DECIMAL(18, 2) AS BEGIN RETURN @amount * 0.2; END; +CREATE FUNCTION dbo.fn_OrdersFor (@id INT) RETURNS TABLE AS RETURN (SELECT * FROM dbo.Orders WHERE CustomerId = @id); +CREATE TRIGGER dbo.trg_Customers_Audit ON dbo.Customers AFTER INSERT, UPDATE AS BEGIN INSERT INTO dbo.AuditLog (EventType) SELECT N'chg' FROM inserted; END; +GRANT SELECT, INSERT ON dbo.Customers TO [AppUser]; +REVOKE DELETE ON dbo.Customers FROM [AppUser]; diff --git a/testdata/realworld/sqlserver/09_admin_dmv.sql b/testdata/realworld/sqlserver/09_admin_dmv.sql new file mode 100644 index 00000000..2f63d471 --- /dev/null +++ b/testdata/realworld/sqlserver/09_admin_dmv.sql @@ -0,0 +1,21 @@ +-- DBA / admin / monitoring queries against catalog views and DMVs +SELECT t.name AS TableName, s.name AS SchemaName, p.rows AS RowCounts FROM sys.tables t JOIN sys.schemas s ON s.schema_id = t.schema_id JOIN sys.partitions p ON p.object_id = t.object_id AND p.index_id IN (0, 1) ORDER BY p.rows DESC; +SELECT TOP (20) qs.total_elapsed_time / qs.execution_count AS AvgElapsed, qs.execution_count, SUBSTRING(st.text, (qs.statement_start_offset / 2) + 1, ((CASE qs.statement_end_offset WHEN -1 THEN DATALENGTH(st.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2) + 1) AS StatementText FROM sys.dm_exec_query_stats AS qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st ORDER BY AvgElapsed DESC; +SELECT r.session_id, r.status, r.wait_type, r.blocking_session_id, DB_NAME(r.database_id) AS DbName FROM sys.dm_exec_requests r WHERE r.session_id > 50 AND r.blocking_session_id <> 0; +SELECT c.name AS ColumnName, ty.name AS TypeName, c.max_length, c.is_nullable FROM sys.columns c JOIN sys.types ty ON ty.user_type_id = c.user_type_id WHERE c.object_id = OBJECT_ID(N'dbo.Customers'); +SELECT i.name, i.type_desc, ips.avg_fragmentation_in_percent FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, N'LIMITED') ips JOIN sys.indexes i ON i.object_id = ips.object_id AND i.index_id = ips.index_id WHERE ips.avg_fragmentation_in_percent > 30; +SELECT name, database_id, create_date, compatibility_level, recovery_model_desc FROM sys.databases WHERE state = 0 ORDER BY name; +SELECT SERVERPROPERTY('ProductVersion') AS Ver, SERVERPROPERTY('Edition') AS Ed; +SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = N'dbo' AND TABLE_NAME = N'Customers' ORDER BY ORDINAL_POSITION; +SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'; +SELECT OBJECT_SCHEMA_NAME(object_id) + N'.' + OBJECT_NAME(object_id) AS FullName FROM sys.objects WHERE type IN ('U', 'V'); +SELECT login_name, COUNT(*) AS Sessions FROM sys.dm_exec_sessions WHERE is_user_process = 1 GROUP BY login_name HAVING COUNT(*) > 5; +SELECT DB_NAME(database_id) AS DatabaseName, SUM(size) * 8 / 1024 AS SizeMB FROM sys.master_files GROUP BY database_id; +SELECT fk.name, OBJECT_NAME(fk.parent_object_id) AS ParentTable, OBJECT_NAME(fk.referenced_object_id) AS RefTable FROM sys.foreign_keys fk; +SELECT TOP (50) wait_type, wait_time_ms, waiting_tasks_count FROM sys.dm_os_wait_stats WHERE wait_type NOT LIKE N'SLEEP%' ORDER BY wait_time_ms DESC; +SELECT * FROM sys.dm_db_missing_index_details; +DBCC CHECKDB (N'Northwind') WITH NO_INFOMSGS; +ALTER INDEX ALL ON dbo.Orders REBUILD WITH (ONLINE = ON); +ALTER INDEX IX_Orders_Status ON dbo.Orders REORGANIZE; +ALTER DATABASE CURRENT SET RECOVERY SIMPLE; +KILL 55; diff --git a/testdata/realworld/sqlserver/10_etl_mixed.sql b/testdata/realworld/sqlserver/10_etl_mixed.sql new file mode 100644 index 00000000..29a34b26 --- /dev/null +++ b/testdata/realworld/sqlserver/10_etl_mixed.sql @@ -0,0 +1,32 @@ +-- Misc production patterns: data cleansing, pagination, search, temporal tables, 2022 functions +SELECT Id, LTRIM(RTRIM(Name)) AS Name, TRY_CONVERT(INT, Age) AS Age FROM dbo.RawImport WHERE TRY_CONVERT(INT, Age) IS NOT NULL; +SELECT CONVERT(DATETIME, '2024-03-15 10:30:00', 120) AS D1, CONVERT(NVARCHAR(30), GETDATE(), 112) AS D2, CONVERT(VARCHAR, GETDATE(), 101) AS D3, CONVERT(DECIMAL(10, 2), N'123.45') AS D4; +SELECT CAST(GETDATE() AS DATE) AS Today, CAST(1 AS BIT) AS B, CAST(N'x' AS VARCHAR(MAX)) AS V; +SELECT DATEFROMPARTS(2024, 1, 31) AS D, DATETIMEFROMPARTS(2024, 1, 31, 12, 0, 0, 0) AS DT, EOMONTH(DATEFROMPARTS(2024, 2, 1)) AS E; +SELECT DATEDIFF_BIG(SECOND, '1970-01-01', GETUTCDATE()) AS Epoch, DATE_BUCKET(WEEK, 1, GETDATE()) AS Wk; +SELECT GREATEST(1, 2, 3) AS G, LEAST(1, 2, 3) AS L; +SELECT DATETRUNC(MONTH, GETDATE()) AS MonthStart; +SELECT IIF(a IS NULL, 0, a) + IIF(b > 0, b, 0) AS Sum2 FROM dbo.T; +SELECT p.* FROM dbo.Products p WHERE p.IsDeleted = 0 AND (p.Name LIKE N'%' + @search + N'%' OR p.Sku = @search) ORDER BY p.Name OFFSET (@page - 1) * @size ROWS FETCH NEXT @size ROWS ONLY; +SELECT COUNT(*) OVER () AS Total, Id, Name FROM dbo.Products ORDER BY Id OFFSET 0 ROWS FETCH FIRST 25 ROWS ONLY; +SELECT * FROM dbo.Employees FOR SYSTEM_TIME AS OF '2024-01-01'; +SELECT * FROM dbo.Employees FOR SYSTEM_TIME BETWEEN '2024-01-01' AND '2024-06-30' WHERE Id = 1; +SELECT * FROM dbo.Employees FOR SYSTEM_TIME ALL ORDER BY ValidFrom; +SELECT * FROM OPENQUERY(LinkedSrv, 'SELECT * FROM remote.dbo.T'); +SELECT * FROM OPENROWSET(BULK 'C:\data\file.csv', SINGLE_CLOB) AS x; +BULK INSERT dbo.Staging FROM 'C:\data\file.csv' WITH (FIELDTERMINATOR = ',', ROWTERMINATOR = '\n', FIRSTROW = 2); +SELECT HASHBYTES('SHA2_256', CONCAT(a, b)) AS H, CHECKSUM(a, b) AS CS, ABS(CHECKSUM(NEWID())) % 100 AS Rnd FROM dbo.T; +SELECT Id, Name, ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS RowNum FROM dbo.T; +SELECT Id FROM dbo.T TABLESAMPLE (10 PERCENT); +SELECT Name, Email FROM dbo.Customers WHERE CONTAINS(Name, N'"smith*"'); +SELECT Name FROM dbo.Customers WHERE FREETEXT(Notes, N'late delivery'); +SELECT COALESCE(NULLIF(LTRIM(RTRIM(a)), ''), N'N/A') FROM dbo.T; +SELECT ISNULL(SUM(Total), 0) FROM dbo.Orders WHERE CustomerId = @cid; +SELECT ROUND(Price * 1.0825, 2) AS WithTax, CEILING(Qty / 10.0) AS Boxes, FLOOR(Price) AS FP, POWER(2, 10) AS P, SQRT(16) AS S FROM dbo.Products; +SELECT TOP (1) WITH TIES CustomerId, Total FROM dbo.Orders ORDER BY Total DESC; +SELECT [Group], [Order], [Select] FROM dbo.[Table]; +SELECT u.[User Name] AS [Display Name] FROM dbo.[User Accounts] AS u; +SELECT x = 1, y = Name FROM dbo.T; +SELECT 'It''s' AS S1, N'Ünïcödé ✓' AS S2, N'日本語' AS S3; +SELECT /* inline comment */ 1 AS a -- trailing comment +; diff --git a/testdata/realworld/sqlserver/11_batches_no_semicolons.sql b/testdata/realworld/sqlserver/11_batches_no_semicolons.sql new file mode 100644 index 00000000..9280ad7a --- /dev/null +++ b/testdata/realworld/sqlserver/11_batches_no_semicolons.sql @@ -0,0 +1,104 @@ +-- T-SQL scripts written without semicolons, as SSMS and sqlcmd accept them: +-- statements end where the next one starts and GO separates batches. +USE [Sales] +GO +SET NOCOUNT ON +SET XACT_ABORT ON +GO +IF OBJECT_ID(N'dbo.OrderAudit', N'U') IS NOT NULL + DROP TABLE dbo.OrderAudit +CREATE TABLE dbo.OrderAudit ( + AuditId INT IDENTITY(1, 1) NOT NULL PRIMARY KEY, + OrderId INT NOT NULL, + Action NVARCHAR(20) NOT NULL, + ChangedAt DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME() +) +CREATE INDEX IX_OrderAudit_OrderId ON dbo.OrderAudit (OrderId) +GO +BEGIN TRANSACTION +UPDATE dbo.Orders SET Status = N'Shipped' WHERE OrderId = 42 +INSERT INTO dbo.OrderAudit (OrderId, Action) VALUES (42, N'ship') +COMMIT TRANSACTION +GO +SET NOCOUNT ON +SELECT COUNT(*) AS OpenOrders FROM dbo.Orders WHERE Status = N'Open' +SET NOCOUNT OFF +GO +DECLARE @Pending TABLE (OrderId INT PRIMARY KEY, Total DECIMAL(18, 2)) +INSERT INTO @Pending (OrderId, Total) +SELECT o.OrderId, o.Total FROM dbo.Orders AS o WHERE o.Status = N'Pending' +UPDATE @Pending SET Total = Total * 1.1 WHERE Total < 100 +DELETE FROM @Pending WHERE Total > 10000 +SELECT p.OrderId, p.Total FROM @Pending AS p JOIN dbo.Customers AS c ON c.CustomerId = p.OrderId +GO +DECLARE @Cutoff DATE = DATEADD(DAY, -30, GETDATE()) +DELETE FROM dbo.Sessions WHERE LastSeen < @Cutoff +PRINT N'Deleted ' + CAST(@@ROWCOUNT AS NVARCHAR(20)) + N' sessions' +GO +UPDATE dbo.Inventory SET Quantity = Quantity - 1 WHERE ProductId = 7 +IF @@ROWCOUNT = 0 + PRINT N'No inventory row for product 7' +GO +ALTER TABLE dbo.Orders NOCHECK CONSTRAINT ALL +ALTER TABLE dbo.Orders ADD ShippedAt DATETIME2 NULL +ALTER TABLE dbo.Orders CHECK CONSTRAINT ALL +GO +TRUNCATE TABLE dbo.StagingOrders +INSERT INTO dbo.StagingOrders (OrderId, Total) SELECT OrderId, Total FROM dbo.Orders WHERE Total > 0 +EXEC dbo.usp_ProcessStaging @BatchSize = 500 +GO +BEGIN TRY + BEGIN TRANSACTION + UPDATE dbo.Accounts SET Balance = Balance - 100 WHERE AccountId = 1 + UPDATE dbo.Accounts SET Balance = Balance + 100 WHERE AccountId = 2 + COMMIT TRANSACTION +END TRY +BEGIN CATCH + ROLLBACK TRANSACTION + THROW +END CATCH +GO +CREATE OR ALTER PROCEDURE dbo.usp_ArchiveOrders + @Before DATE +AS +BEGIN + SET NOCOUNT ON + INSERT INTO dbo.OrdersArchive (OrderId, Total) SELECT OrderId, Total FROM dbo.Orders WHERE OrderDate < @Before + DELETE FROM dbo.Orders WHERE OrderDate < @Before +END +GO +EXEC dbo.usp_ArchiveOrders @Before = '2024-01-01' +SELECT TOP (10) OrderId, Total FROM dbo.OrdersArchive ORDER BY OrderId DESC +GO +WITH Ranked AS ( + SELECT CustomerId, Total, ROW_NUMBER() OVER (PARTITION BY CustomerId ORDER BY Total DESC) AS rn + FROM dbo.Orders +) +SELECT CustomerId, Total FROM Ranked WHERE rn = 1 +SELECT 1 AS Done +GO +IF OBJECT_ID(N'tempdb..#Totals') IS NOT NULL DROP TABLE #Totals +SELECT CustomerId, SUM(Total) AS Total INTO #Totals FROM dbo.Orders GROUP BY CustomerId +IF EXISTS (SELECT 1 FROM #Totals WHERE Total > 100000) + PRINT N'Large customers found' +ELSE + PRINT N'No large customers' +DROP TABLE #Totals +GO +DECLARE @Retries INT = 0 +WHILE @Retries < 3 +BEGIN + SET @Retries = @Retries + 1 + WAITFOR DELAY '00:00:01' +END +SELECT @Retries AS Retries +GO +BEGIN TRANSACTION +DELETE FROM dbo.Carts WHERE UpdatedAt < DATEADD(DAY, -7, GETDATE()) +IF @@ERROR <> 0 ROLLBACK TRANSACTION ELSE COMMIT TRANSACTION +GO +GRANT SELECT, INSERT ON dbo.OrderAudit TO [ReportingRole] +DENY DELETE ON dbo.OrderAudit TO [ReportingRole] +REVOKE UPDATE ON dbo.Orders FROM [LegacyApp] +SELECT name FROM sys.database_principals WHERE type = N'R' +GO From fb1ffcb9ace9684af3c8b85831d1132bfdfd7629 Mon Sep 17 00:00:00 2001 From: Ajit Pratap Singh Date: Tue, 27 Oct 2026 03:03:13 +0530 Subject: [PATCH 2/2] test(realworld): flag malformed known-failure entries and truncate by rune Remove a comment that described a check this branch does not contain, report malformed and duplicate known_failures.txt entries instead of skipping them, note that statement indexes shift when a corpus file changes, and truncate report text on rune boundaries. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01DnbRnTFErhoMJdAjVEeCg2 --- pkg/gosqlx/realworld_corpus_test.go | 16 ++++++++++------ scripts/dialect-audit/main.go | 4 ++-- testdata/realworld/known_failures.txt | 1 + 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/pkg/gosqlx/realworld_corpus_test.go b/pkg/gosqlx/realworld_corpus_test.go index dddcddc5..b915f8fd 100644 --- a/pkg/gosqlx/realworld_corpus_test.go +++ b/pkg/gosqlx/realworld_corpus_test.go @@ -46,10 +46,6 @@ func TestRealWorldCorpus(t *testing.T) { if err != nil { t.Fatalf("read %s: %v", realworldRoot, err) } - // After every statement has been parsed, formatted and re-parsed, the - // shared read-only stand-ins returned by the DialectExtras/DialectModifiers - // accessors must still be empty: a write through one would leak into every - // AST that lacks its own extras. known := loadKnownFailures(t) seen := map[string]bool{} var current []string // "key stage" for every failing statement @@ -143,6 +139,7 @@ func writeKnownFailures(t *testing.T, lines []string) { header := "# Real-world corpus statements that GoSQLX does not yet handle.\n" + "# Format: /# \n" + "# Stages: parse, reparse, idempotent, drop (formatter lost words), panic.\n" + + "# Indexes shift when statements are added to or removed from a file: regenerate then.\n" + "# Regenerate: go test ./pkg/gosqlx -run TestRealWorldCorpus -update-known-failures\n" data := header + strings.Join(lines, "\n") + "\n" if err := os.WriteFile(filepath.Join(realworldRoot, "known_failures.txt"), []byte(data), 0o644); err != nil { @@ -173,9 +170,16 @@ func loadKnownFailures(t *testing.T) map[string]string { line = line[:i] } fields := strings.Fields(line) + if len(fields) == 0 { + continue + } if len(fields) != 2 { + t.Errorf("known_failures.txt: malformed entry %q (want \"file.sql#N stage\")", sc.Text()) continue } + if _, dup := known[fields[0]]; dup { + t.Errorf("known_failures.txt: duplicate entry %s", fields[0]) + } known[fields[0]] = fields[1] } if err := sc.Err(); err != nil { @@ -195,8 +199,8 @@ func onlyComments(s string) bool { func compact(s string) string { s = strings.Join(strings.Fields(s), " ") - if len(s) > 240 { - s = s[:240] + "…" + if r := []rune(s); len(r) > 240 { + s = string(r[:240]) + "…" } return s } diff --git a/scripts/dialect-audit/main.go b/scripts/dialect-audit/main.go index 12a05d81..274bcee1 100644 --- a/scripts/dialect-audit/main.go +++ b/scripts/dialect-audit/main.go @@ -136,8 +136,8 @@ func stripComments(s string) string { func oneLine(s string) string { s = strings.Join(strings.Fields(s), " ") - if len(s) > 300 { - s = s[:300] + "…" + if r := []rune(s); len(r) > 300 { + s = string(r[:300]) + "…" } return s } diff --git a/testdata/realworld/known_failures.txt b/testdata/realworld/known_failures.txt index f277a179..24e682c6 100644 --- a/testdata/realworld/known_failures.txt +++ b/testdata/realworld/known_failures.txt @@ -1,6 +1,7 @@ # Real-world corpus statements that GoSQLX does not yet handle. # Format: /# # Stages: parse, reparse, idempotent, drop (formatter lost words), panic. +# Indexes shift when statements are added to or removed from a file: regenerate then. # Regenerate: go test ./pkg/gosqlx -run TestRealWorldCorpus -update-known-failures clickhouse/01_app_crud.sql#10 parse clickhouse/01_app_crud.sql#11 parse