Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ __pycache__
.devenv*
devenv.local.nix

# Internal planning / research notes
/plans/

9 changes: 9 additions & 0 deletions docs/reference/changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
# Changelog
All notable changes to this project will be documented in this file.

## [Unreleased]

### Features

- (oracle) Add experimental Oracle database support for the built-in Go code
generator: PL/SQL parser (ANTLR grammars-v4), catalog with built-in
functions, and Oracle-to-Go type mapping targeting `github.com/sijms/go-ora/v2`.
Enable with `engine: "oracle"`.

(v1-31-1)=
## [1.31.1](https://github.com/sqlc-dev/sqlc/releases/tag/v1.31.1)
Released 2026-04-22
Expand Down
3 changes: 2 additions & 1 deletion docs/reference/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ Each mapping in the `sql` collection has the following keys:
- `name`:
- An human-friendly identifier for this query set. Optional.
- `engine`:
- One of `postgresql`, `mysql` or `sqlite`.
- One of `postgresql`, `mysql`, `sqlite` or `oracle`. The `oracle` engine is
experimental and currently supported only by the built-in Go code generator.
- `schema`:
- Directory of SQL migrations or path to single SQL file; or a list of paths.
- `queries`:
Expand Down
18 changes: 9 additions & 9 deletions docs/reference/language-support.rst
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
Database and language support
#############################

========== ======================= ============ ============ ===============
Language Plugin MySQL PostgreSQL SQLite
========== ======================= ============ ============ ===============
Go (built-in) Stable Stable Beta
Go `sqlc-gen-go`_ Stable Stable Beta
Kotlin `sqlc-gen-kotlin`_ Beta Beta Not implemented
Python `sqlc-gen-python`_ Beta Beta Not implemented
TypeScript `sqlc-gen-typescript`_ Beta Beta Not implemented
========== ======================= ============ ============ ===============
========== ======================= ============ ============ =============== ===============
Language Plugin MySQL PostgreSQL SQLite Oracle
========== ======================= ============ ============ =============== ===============
Go (built-in) Stable Stable Beta Experimental
Go `sqlc-gen-go`_ Stable Stable Beta Not implemented
Kotlin `sqlc-gen-kotlin`_ Beta Beta Not implemented Not implemented
Python `sqlc-gen-python`_ Beta Beta Not implemented Not implemented
TypeScript `sqlc-gen-typescript`_ Beta Beta Not implemented Not implemented
========== ======================= ============ ============ =============== ===============

Community language support
**************************
Expand Down
8 changes: 6 additions & 2 deletions internal/cmd/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/sqlc-dev/sqlc/internal/engine/clickhouse"
"github.com/sqlc-dev/sqlc/internal/engine/dolphin"
"github.com/sqlc-dev/sqlc/internal/engine/oracle"
"github.com/sqlc-dev/sqlc/internal/engine/postgresql"
"github.com/sqlc-dev/sqlc/internal/engine/sqlite"
"github.com/sqlc-dev/sqlc/internal/sql/ast"
Expand Down Expand Up @@ -39,7 +40,7 @@ Examples:
return err
}
if dialect == "" {
return fmt.Errorf("--dialect flag is required (postgresql, mysql, sqlite, or clickhouse)")
return fmt.Errorf("--dialect flag is required (postgresql, mysql, sqlite, oracle, or clickhouse)")
}

// Determine input source
Expand Down Expand Up @@ -75,11 +76,14 @@ Examples:
case "sqlite":
parser := sqlite.NewParser()
stmts, err = parser.Parse(input)
case "oracle", "plsql":
parser := oracle.NewParser()
stmts, err = parser.Parse(input)
case "clickhouse":
parser := clickhouse.NewParser()
stmts, err = parser.Parse(input)
default:
return fmt.Errorf("unsupported dialect: %s (use postgresql, mysql, sqlite, or clickhouse)", dialect)
return fmt.Errorf("unsupported dialect: %s (use postgresql, mysql, sqlite, oracle, or clickhouse)", dialect)
}
if err != nil {
return fmt.Errorf("parse error: %w", err)
Expand Down
2 changes: 2 additions & 0 deletions internal/codegen/golang/go_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ func goInnerType(req *plugin.GenerateRequest, options *opts.Options, col *plugin
return postgresType(req, options, col)
case "sqlite":
return sqliteType(req, options, col)
case "oracle":
return oracleType(req, options, col)
default:
return "interface{}"
}
Expand Down
156 changes: 156 additions & 0 deletions internal/codegen/golang/oracle_type.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package golang

import (
"log"
"strings"

"github.com/sqlc-dev/sqlc/internal/codegen/golang/opts"
"github.com/sqlc-dev/sqlc/internal/codegen/sdk"
"github.com/sqlc-dev/sqlc/internal/debug"
"github.com/sqlc-dev/sqlc/internal/plugin"
)

// oracleType maps an Oracle column type to a Go type. It targets the
// github.com/sijms/go-ora/v2 driver, which implements database/sql, so the
// nullable variants use the standard sql.Null* wrappers (or pointers when
// EmitPointersForNullTypes is set), mirroring the SQLite engine's mapping.
//
// Oracle type semantics (per go-ora):
// - NUMBER with scale 0 -> integer (int64)
// - NUMBER with scale > 0 -> float64
// sqlc does not track scale in the built-in analyzer, so bare NUMBER maps
// to float64 to avoid silently truncating fractional values; INTEGER/INT and
// the PL/SQL integer types map to int64.
// - VARCHAR2/CHAR/CLOB/etc. -> string
// - DATE/TIMESTAMP* -> time.Time
// - RAW/BLOB/LONG RAW/BFILE -> []byte
// - BINARY_FLOAT/BINARY_DOUBLE -> float64
func oracleType(req *plugin.GenerateRequest, options *opts.Options, col *plugin.Column) string {
dt := strings.ToLower(sdk.DataType(col.Type))
notNull := col.NotNull || col.IsArray
emitPointersForNull := options.EmitPointersForNullTypes

nullableString := func() string {
if notNull {
return "string"
}
if emitPointersForNull {
return "*string"
}
return "sql.NullString"
}
nullableInt64 := func() string {
if notNull {
return "int64"
}
if emitPointersForNull {
return "*int64"
}
return "sql.NullInt64"
}
nullableFloat64 := func() string {
if notNull {
return "float64"
}
if emitPointersForNull {
return "*float64"
}
return "sql.NullFloat64"
}
nullableTime := func() string {
if notNull {
return "time.Time"
}
if emitPointersForNull {
return "*time.Time"
}
return "sql.NullTime"
}
nullableBool := func() string {
if notNull {
return "bool"
}
if emitPointersForNull {
return "*bool"
}
return "sql.NullBool"
}

switch dt {

// Integer types.
case "integer", "int", "smallint",
"binary_integer", "pls_integer", "simple_integer",
"natural", "naturaln", "positive", "positiven", "signtype":
return nullableInt64()

// Fixed / floating point numeric types.
case "number", "numeric", "dec", "decimal",
"float", "real", "double precision", "double",
"binary_float", "binary_double":
return nullableFloat64()

// Boolean (Oracle 23c native BOOLEAN / PL/SQL BOOLEAN).
case "boolean", "bool":
return nullableBool()

// Date / time types.
case "date",
"timestamp",
"timestamp_unconstrained",
"timestamp_tz_unconstrained",
"timestamp_ltz_unconstrained":
return nullableTime()

// Binary types.
case "raw", "long raw", "longraw", "blob", "bfile":
return "[]byte"

// XML.
case "xmltype":
return nullableString()

case "any":
return "interface{}"
}

// Prefix-based matches for parameterized types (e.g. VARCHAR2(100),
// TIMESTAMP(6), TIMESTAMP WITH TIME ZONE, INTERVAL ...).
switch {
case strings.HasPrefix(dt, "varchar2"),
strings.HasPrefix(dt, "nvarchar2"),
strings.HasPrefix(dt, "varchar"),
strings.HasPrefix(dt, "char"),
strings.HasPrefix(dt, "nchar"),
strings.HasPrefix(dt, "character"),
strings.HasPrefix(dt, "clob"),
strings.HasPrefix(dt, "nclob"),
strings.HasPrefix(dt, "long"),
strings.HasPrefix(dt, "string"),
strings.HasPrefix(dt, "rowid"),
strings.HasPrefix(dt, "urowid"):
return nullableString()

case strings.HasPrefix(dt, "timestamp"):
return nullableTime()

case strings.HasPrefix(dt, "interval"):
// Oracle INTERVAL types are surfaced as strings by go-ora.
return nullableString()

case strings.HasPrefix(dt, "number"),
strings.HasPrefix(dt, "numeric"),
strings.HasPrefix(dt, "decimal"),
strings.HasPrefix(dt, "dec"),
strings.HasPrefix(dt, "float"):
return nullableFloat64()

case strings.HasPrefix(dt, "raw"):
return "[]byte"
}

if debug.Active {
log.Printf("unknown Oracle type: %s\n", dt)
}
return "interface{}"
}
57 changes: 57 additions & 0 deletions internal/codegen/golang/oracle_type_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package golang

import (
"testing"

"github.com/sqlc-dev/sqlc/internal/codegen/golang/opts"
"github.com/sqlc-dev/sqlc/internal/plugin"
)

func TestOracleType(t *testing.T) {
req := &plugin.GenerateRequest{Settings: &plugin.Settings{Engine: "oracle"}}

cases := []struct {
name string
dataType string
notNull bool
emitPtr bool
want string
}{
{"number nullable", "number", false, false, "sql.NullFloat64"},
{"number not null", "number", true, false, "float64"},
{"number ptr", "number", false, true, "*float64"},
{"integer not null", "integer", true, false, "int64"},
{"int nullable", "int", false, false, "sql.NullInt64"},
{"varchar2 not null", "varchar2", true, false, "string"},
{"varchar2(100) not null", "varchar2(100)", true, false, "string"},
{"nvarchar2 nullable", "nvarchar2", false, false, "sql.NullString"},
{"char not null", "char", true, false, "string"},
{"clob not null", "clob", true, false, "string"},
{"date not null", "date", true, false, "time.Time"},
{"timestamp nullable", "timestamp", false, false, "sql.NullTime"},
{"timestamp(6) not null", "timestamp(6)", true, false, "time.Time"},
{"raw", "raw", true, false, "[]byte"},
{"blob", "blob", true, false, "[]byte"},
{"binary_double not null", "binary_double", true, false, "float64"},
{"boolean not null", "boolean", true, false, "bool"},
{"boolean nullable", "boolean", false, false, "sql.NullBool"},
{"interval", "interval", true, false, "string"},
{"rowid", "rowid", true, false, "string"},
{"unknown", "sdo_geometry_xyz", true, false, "interface{}"},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
options := &opts.Options{EmitPointersForNullTypes: tc.emitPtr}
col := &plugin.Column{
Type: &plugin.Identifier{Name: tc.dataType},
NotNull: tc.notNull,
}
got := oracleType(req, options, col)
if got != tc.want {
t.Errorf("oracleType(%q, notNull=%v, emitPtr=%v) = %q, want %q",
tc.dataType, tc.notNull, tc.emitPtr, got, tc.want)
}
})
}
}
5 changes: 5 additions & 0 deletions internal/compiler/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/sqlc-dev/sqlc/internal/config"
"github.com/sqlc-dev/sqlc/internal/dbmanager"
"github.com/sqlc-dev/sqlc/internal/engine/dolphin"
"github.com/sqlc-dev/sqlc/internal/engine/oracle"
"github.com/sqlc-dev/sqlc/internal/engine/postgresql"
pganalyze "github.com/sqlc-dev/sqlc/internal/engine/postgresql/analyzer"
"github.com/sqlc-dev/sqlc/internal/engine/sqlite"
Expand Down Expand Up @@ -82,6 +83,10 @@ func NewCompiler(conf config.SQL, combo config.CombinedSettings, parserOpts opts
c.parser = dolphin.NewParser()
c.catalog = dolphin.NewCatalog()
c.selector = newDefaultSelector()
case config.EngineOracle:
c.parser = oracle.NewParser()
c.catalog = oracle.NewCatalog()
c.selector = newDefaultSelector()
case config.EnginePostgreSQL:
parser := postgresql.NewParser()
c.parser = parser
Expand Down
1 change: 1 addition & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const (
EngineMySQL Engine = "mysql"
EnginePostgreSQL Engine = "postgresql"
EngineSQLite Engine = "sqlite"
EngineOracle Engine = "oracle"
)

type Config struct {
Expand Down
9 changes: 6 additions & 3 deletions internal/config/v_one.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
"enum": [
"postgresql",
"mysql",
"sqlite"
"sqlite",
"oracle"
]
},
"schema": {
Expand Down Expand Up @@ -200,7 +201,8 @@
"enum": [
"postgresql",
"mysql",
"sqlite"
"sqlite",
"oracle"
]
},
"nullable": {
Expand Down Expand Up @@ -320,7 +322,8 @@
"enum": [
"postgresql",
"mysql",
"sqlite"
"sqlite",
"oracle"
]
},
"nullable": {
Expand Down
Loading
Loading