Skip to content

Conversation

@simukka
Copy link
Contributor

@simukka simukka commented Jan 8, 2026

SQLDocument provides an interface for handling SQL files.
This allows us to abstracts the logic for specific SQL languages.
Currently, this is mostly a refactor of the current logic for handling T-SQL files. But allows us to easily introduce support for pgsql languages.

The intent with this PR is to first cleanup and organize how we implement SQL language specific token parsing and handling without changing or breaking any current features or uses of sqlcode.

The goal is for us to easily introduce new SQL languages, such as Postgresql (next pr)

Other improvements:

  • Updated testing to be more modular.
  • Added more unit testing for T-SQL language syntax.
  • Updated GO to 1.25.
  • Updated the mssql driver to microsoft/go-mssqldb.

Related PRs

API changes:

(new) CodePatch(dbc *sql.DB, sql string) is similar to Patch, but requires a Database instance to correctly patch the provided SQL text for the current SQL driver. https://github.com/vippsas/sqlcode/pull/66/files#diff-abfd003352914455b11a902e7b7ca94b0672651ae390d52235cf282090483b53R256

┌─────────────────────────────────────────────────────────────────────────────────┐
│                              SQLCODE ARCHITECTURE                               │
└─────────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────────┐
│                                 USER APPLICATION                                │
│                                                                                 │
│   //go:embed sql/*.sql                                                          │
│   var sqlFS embed.FS                                                            │
│   deployable := sqlcode.MustInclude(sqlcode.Options{}, sqlFS)                   │
│   deployable.EnsureUploaded(ctx, db)                                            │
│   db.Query(deployable.CodePatch(db, "SELECT * FROM [code].MyProc"))             │
│                                                                                 │
└───────────────────────────────────┬─────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────────────┐
│                               DEPLOYABLE                                        │
│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────────────────────┐  │
│  │  SchemaSuffix   │  │  ParsedFiles    │  │  CodeBase (Document)            │  │
│  │  (hash-based)   │  │  (file list)    │  │  - Declares[]                   │  │
│  │  e.g. "a1b2c3"  │  │                 │  │  - Creates[]                    │  │
│  └─────────────────┘  └─────────────────┘  └─────────────────────────────────┘  │
│                                                                                 │
│  Methods:                                                                       │
│  ├── Include(opts, fs...)      Parse SQL files from embedded filesystem        │
│  ├── EnsureUploaded(ctx, db)   Upload if not exists (with locking)             │
│  ├── Upload(ctx, db)           Create schema and deploy procedures             │
│  ├── DropAndUpload(ctx, db)    Drop existing and redeploy                      │
│  ├── CodePatch(db, sql)        Replace [code] with actual schema name          │
│  ├── IntConst(name)            Get declared constant value                     │
│  └── ListUploaded(ctx, db)     List all deployed schemas                       │
└───────────────────────────────────┬─────────────────────────────────────────────┘
                                    │
                    ┌───────────────┴───────────────┐
                    ▼                               ▼
┌─────────────────────────────────┐  ┌─────────────────────────────────────────────┐
│         PARSER                  │  │              PREPROCESSOR                   │
│  (sqlparser package)            │  │                                             │
│                                 │  │  Transforms [code] → [code@<suffix>]        │
│  ┌───────────────────────────┐  │  │  Replaces @Enum/@Const with literals        │
│  │  Scanner                  │  │  │  Tracks line number corrections             │
│  │  - Tokenizes SQL          │  │  │                                             │
│  │  - T-SQL / PostgreSQL     │  │  │  ┌─────────────────────────────────────┐    │
│  └───────────────────────────┘  │  │  │  PreprocessedFile                   │    │
│              │                  │  │  │  └── Batches[]                      │    │
│              ▼                  │  │  │      ├── StartPos                   │    │
│  ┌───────────────────────────┐  │  │  │      ├── Lines (transformed SQL)    │    │
│  │  TSqlDocument             │  │  │  │      └── lineNumberCorrections      │    │
│  │  - Parse()                │  │  │  └─────────────────────────────────────┘    │
│  │  - Sort() (topological)   │  │  └─────────────────────────────────────────────┘
│  │  - Include()              │  │
│  └───────────────────────────┘  │
│              │                  │
│              ▼                  │
│  ┌───────────────────────────┐  │
│  │  Document (interface)     │  │
│  │  ├── Declares[]           │  │
│  │  │   ├── VariableName     │  │
│  │  │   ├── Datatype         │  │
│  │  │   └── Literal          │  │
│  │  ├── Creates[]            │  │
│  │  │   ├── CreateType       │  │
│  │  │   ├── QuotedName       │  │
│  │  │   ├── Body[]           │  │
│  │  │   ├── DependsOn[]      │  │
│  │  │   └── Docstring[]      │  │
│  │  └── Errors[]             │  │
│  └───────────────────────────┘  │
└─────────────────────────────────┘
                                    
┌─────────────────────────────────────────────────────────────────────────────────┐
│                              DATABASE LAYER                                     │
│                                                                                 │
│  ┌─────────────────────────────┐    ┌─────────────────────────────────────────┐ │
│  │  SQL Server (MSSQL)         │    │  PostgreSQL                             │ │
│  │                             │    │                                         │ │
│  │  Schema: [code@a1b2c3]      │    │  Schema: "code@a1b2c3"                  │ │
│  │                             │    │                                         │ │
│  │  ┌───────────────────────┐  │    │  ┌───────────────────────────────────┐  │ │
│  │  │ sqlcode.CreateCode-   │  │    │  │ sqlcode.createcodeschema()        │  │ │
│  │  │ Schema (stored proc)  │  │    │  │                                   │  │ │
│  │  └───────────────────────┘  │    │  └───────────────────────────────────┘  │ │
│  │                             │    │                                         │ │
│  │  Security:                  │    │  Security:                              │ │
│  │  - sqlcode-deploy-sandbox  │    │  - sqlcode-deploy-sandbox-user          │ │
│  │    -user impersonation     │    │    role switching                       │ │
│  │  - execute as / revert     │    │  - set role / reset role                │ │
│  │                             │    │                                         │ │
│  │  Locking:                   │    │  Locking:                               │ │
│  │  - sp_getapplock           │    │  - sqlcode.get_applock()                │ │
│  │  - sp_releaseapplock       │    │  - sqlcode.release_applock()            │ │
│  └─────────────────────────────┘    └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────────┐
│                              SQL FILE FORMAT                                    │
│                                                                                 │
│  ┌────────────────────────────────────────────────────────────────────────────┐ │
│  │  -- File: procedures.sql                                                   │ │
│  │  --sqlcode:include-if production,staging                                   │ │
│  │                                                                            │ │
│  │  declare @EnumActive int = 1, @EnumInactive int = 0;                       │ │
│  │  declare @ConstMaxRetries int = 3;                                         │ │
│  │  go                                                                        │ │
│  │                                                                            │ │
│  │  -- Get user by ID                                                         │ │
│  │  create procedure [code].GetUser                                           │ │
│  │      @UserId int                                                           │ │
│  │  as                                                                        │ │
│  │  begin                                                                     │ │
│  │      select * from Users                                                   │ │
│  │      where Id = @UserId and Status = @EnumActive                           │ │
│  │  end                                                                       │ │
│  │  go                                                                        │ │
│  │                                                                            │ │
│  │  create procedure [code].ProcessUser                                       │ │
│  │      @UserId int                                                           │ │
│  │  as                                                                        │ │
│  │  begin                                                                     │ │
│  │      exec [code].GetUser @UserId  -- dependency tracked                    │ │
│  │  end                                                                       │ │
│  └────────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────────┐
│                              DEPLOYMENT FLOW                                    │
│                                                                                 │
│   1. PARSE                2. HASH               3. UPLOAD                       │
│   ┌──────────┐           ┌──────────┐          ┌──────────────────────┐         │
│   │ .sql     │──Parse───▶│ Document │──SHA256─▶│ Schema: code@a1b2c3  │         │
│   │ files    │           │ AST      │          │                      │         │
│   └──────────┘           └──────────┘          │ ┌──────────────────┐ │         │
│                                │               │ │ GetUser          │ │         │
│                                │               │ │ ProcessUser      │ │         │
│                          Sort by deps          │ │ (sorted order)   │ │         │
│                                │               │ └──────────────────┘ │         │
│                                ▼               └──────────────────────┘         │
│                          ┌──────────┐                                           │
│                          │ Sorted   │                                           │
│                          │ Creates  │                                           │
│                          └──────────┘                                           │
│                                                                                 │
│   4. QUERY (at runtime)                                                         │
│   ┌─────────────────────────────────────────────────────────────────────────┐   │
│   │  db.Query(deployable.CodePatch(db, "EXEC [code].GetUser @id"))          │   │
│   │                              ▼                                          │   │
│   │  db.Query("EXEC [code@a1b2c3].GetUser @id")                             │   │
│   └─────────────────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────────┐
│                           KEY CONCEPTS                                          │
│                                                                                 │
│  ┌─────────────────┐  Hash-based schema names enable:                           │
│  │ IMMUTABLE       │  • Zero-downtime deployments                               │
│  │ SCHEMAS         │  • Multiple versions coexisting                            │
│  └─────────────────┘  • Automatic rollback (just point to old hash)             │
│                                                                                 │
│  ┌─────────────────┐  Variables prefixed with @Enum/@Const/@Global:             │
│  │ CONSTANTS       │  • Replaced at deploy time with literal values             │
│  └─────────────────┘  • Enables compile-time constants in SQL                   │
│                                                                                 │
│  ┌─────────────────┐  Creates are sorted before deployment:                     │
│  │ DEPENDENCY      │  • Dependencies extracted from [code].Name references      │
│  │ ORDERING        │  • Topological sort ensures correct order                  │
│  └─────────────────┘  • Circular dependencies detected and reported             │
│                                                                                 │
│  ┌─────────────────┐  Sandboxed execution:                                      │
│  │ SECURITY        │  • Deploy runs as restricted user                          │
│  └─────────────────┘  • Prevents malicious SQL in migrations                    │
└─────────────────────────────────────────────────────────────────────────────────┘

simukka added 30 commits January 8, 2026 12:17
…etermine the driver type. Allow for future expansion/testing with other drivers. Preparring to write sqlcode migration for Postgres.
@simukka simukka changed the title Sqldocument SQLDocument Jan 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants