diff --git a/_examples/a_bit_of_everything/postgres/apartitiontable.xo.go b/_examples/a_bit_of_everything/postgres/apartitiontable.xo.go
new file mode 100644
index 00000000..46377457
--- /dev/null
+++ b/_examples/a_bit_of_everything/postgres/apartitiontable.xo.go
@@ -0,0 +1,94 @@
+package postgres
+
+// Code generated by xo. DO NOT EDIT.
+
+import (
+ "context"
+ "time"
+)
+
+// APartitionTable represents a row from 'public.a_partition_table'.
+type APartitionTable struct {
+ AKey1 int `json:"a_key1"` // a_key1
+ AKey2 time.Time `json:"a_key2"` // a_key2
+ // xo fields
+ _exists, _deleted bool
+}
+
+// Exists returns true when the [APartitionTable] exists in the database.
+func (apt *APartitionTable) Exists() bool {
+ return apt._exists
+}
+
+// Deleted returns true when the [APartitionTable] has been marked for deletion
+// from the database.
+func (apt *APartitionTable) Deleted() bool {
+ return apt._deleted
+}
+
+// Insert inserts the [APartitionTable] to the database.
+func (apt *APartitionTable) Insert(ctx context.Context, db DB) error {
+ switch {
+ case apt._exists: // already exists
+ return logerror(&ErrInsertFailed{ErrAlreadyExists})
+ case apt._deleted: // deleted
+ return logerror(&ErrInsertFailed{ErrMarkedForDeletion})
+ }
+ // insert (manual)
+ const sqlstr = `INSERT INTO public.a_partition_table (` +
+ `a_key1, a_key2` +
+ `) VALUES (` +
+ `$1, $2` +
+ `)`
+ // run
+ logf(sqlstr, apt.AKey1, apt.AKey2)
+ if _, err := db.ExecContext(ctx, sqlstr, apt.AKey1, apt.AKey2); err != nil {
+ return logerror(err)
+ }
+ // set exists
+ apt._exists = true
+ return nil
+}
+
+// ------ NOTE: Update statements omitted due to lack of fields other than primary key ------
+
+// Delete deletes the [APartitionTable] from the database.
+func (apt *APartitionTable) Delete(ctx context.Context, db DB) error {
+ switch {
+ case !apt._exists: // doesn't exist
+ return nil
+ case apt._deleted: // deleted
+ return nil
+ }
+ // delete with composite primary key
+ const sqlstr = `DELETE FROM public.a_partition_table ` +
+ `WHERE a_key1 = $1 AND a_key2 = $2`
+ // run
+ logf(sqlstr, apt.AKey1, apt.AKey2)
+ if _, err := db.ExecContext(ctx, sqlstr, apt.AKey1, apt.AKey2); err != nil {
+ return logerror(err)
+ }
+ // set deleted
+ apt._deleted = true
+ return nil
+}
+
+// APartitionTableByAKey1AKey2 retrieves a row from 'public.a_partition_table' as a [APartitionTable].
+//
+// Generated from index 'a_partition_table_pkey'.
+func APartitionTableByAKey1AKey2(ctx context.Context, db DB, aKey1 int, aKey2 time.Time) (*APartitionTable, error) {
+ // query
+ const sqlstr = `SELECT ` +
+ `a_key1, a_key2 ` +
+ `FROM public.a_partition_table ` +
+ `WHERE a_key1 = $1 AND a_key2 = $2`
+ // run
+ logf(sqlstr, aKey1, aKey2)
+ apt := APartitionTable{
+ _exists: true,
+ }
+ if err := db.QueryRowContext(ctx, sqlstr, aKey1, aKey2).Scan(&apt.AKey1, &apt.AKey2); err != nil {
+ return nil, logerror(err)
+ }
+ return &apt, nil
+}
diff --git a/_examples/a_bit_of_everything/postgres/xo.xo.dot b/_examples/a_bit_of_everything/postgres/xo.xo.dot
index 7dd4daaf..02f433f4 100644
--- a/_examples/a_bit_of_everything/postgres/xo.xo.dot
+++ b/_examples/a_bit_of_everything/postgres/xo.xo.dot
@@ -107,6 +107,13 @@ digraph public {
| "public.a_manual_table" |
| a_text: character varying |
> ]
+
+ "public.a_partition_table" [ label=<
+
+ | "public.a_partition_table" |
+ | a_key1: integer |
+ | a_key2: timestamp without time zone |
+
> ]
"public.a_primary" [ label=<
diff --git a/_examples/a_bit_of_everything/postgres/xo.xo.json b/_examples/a_bit_of_everything/postgres/xo.xo.json
index 0368afde..bfeb24b1 100644
--- a/_examples/a_bit_of_everything/postgres/xo.xo.json
+++ b/_examples/a_bit_of_everything/postgres/xo.xo.json
@@ -857,6 +857,69 @@
],
"manual": true
},
+ {
+ "type":"table",
+ "name":"a_partition_table",
+ "columns":[
+ {
+ "name":"a_key1",
+ "datatype":{
+ "type":"integer"
+ },
+ "is_primary":true
+ },
+ {
+ "name":"a_key2",
+ "datatype":{
+ "type":"timestamp without time zone"
+ },
+ "is_primary":true
+ }
+ ],
+ "primary_keys":[
+ {
+ "name":"a_key1",
+ "datatype":{
+ "type":"integer"
+ },
+ "is_primary":true
+ },
+ {
+ "name":"a_key2",
+ "datatype":{
+ "type":"timestamp without time zone"
+ },
+ "is_primary":true
+ }
+ ],
+ "indexes":[
+ {
+ "name":"a_partition_table_pkey",
+ "fields":[
+ {
+ "name":"a_key1",
+ "datatype":{
+ "type":"integer"
+ },
+ "is_primary":true
+ },
+ {
+ "name":"a_key2",
+ "datatype":{
+ "type":"timestamp without time zone"
+ },
+ "is_primary":true
+ }
+ ],
+ "is_unique":true,
+ "is_primary":true
+ }
+ ],
+ "manual":true,
+ "partition": {
+ "definition": "RANGE (a_key2)"
+ }
+ },
{
"type": "table",
"name": "a_primary",
diff --git a/_examples/a_bit_of_everything/postgres/xo.xo.sql b/_examples/a_bit_of_everything/postgres/xo.xo.sql
index a5115dc7..6d58e3e9 100644
--- a/_examples/a_bit_of_everything/postgres/xo.xo.sql
+++ b/_examples/a_bit_of_everything/postgres/xo.xo.sql
@@ -127,6 +127,14 @@ CREATE TABLE a_manual_table (
a_text VARCHAR(255)
);
+-- table a_partition_table
+CREATE TABLE a_partition_table (
+ a_key1 INTEGER NOT NULL,
+ a_key2 TIMESTAMP NOT NULL,
+ PRIMARY KEY (a_key1, a_key2)
+)
+PARTITION BY RANGE (a_key2);
+
-- table a_primary_multi
CREATE TABLE a_primary_multi (
a_key INTEGER NOT NULL,
diff --git a/_examples/a_bit_of_everything/postgres/xo.xo.yaml b/_examples/a_bit_of_everything/postgres/xo.xo.yaml
index e3ef6093..63924e8b 100644
--- a/_examples/a_bit_of_everything/postgres/xo.xo.yaml
+++ b/_examples/a_bit_of_everything/postgres/xo.xo.yaml
@@ -495,6 +495,42 @@ schemas:
prec: 255
nullable: true
manual: true
+ - type: table
+ name: a_partition_table
+ columns:
+ - name: a_key1
+ datatype:
+ type: integer
+ is_primary: true
+ - name: a_key2
+ datatype:
+ type: timestamp without time zone
+ is_primary: true
+ primary_keys:
+ - name: a_key1
+ datatype:
+ type: integer
+ is_primary: true
+ - name: a_key2
+ datatype:
+ type: timestamp without time zone
+ is_primary: true
+ indexes:
+ - name: a_partition_table_pkey
+ fields:
+ - name: a_key1
+ datatype:
+ type: integer
+ is_primary: true
+ - name: a_key2
+ datatype:
+ type: timestamp without time zone
+ is_primary: true
+ is_unique: true
+ is_primary: true
+ manual: true
+ partition:
+ definition: RANGE (a_key2)
- type: table
name: a_primary
columns:
diff --git a/_examples/a_bit_of_everything/sql/postgres_schema.sql b/_examples/a_bit_of_everything/sql/postgres_schema.sql
index c90f1ff5..fab6876e 100644
--- a/_examples/a_bit_of_everything/sql/postgres_schema.sql
+++ b/_examples/a_bit_of_everything/sql/postgres_schema.sql
@@ -83,6 +83,13 @@ CREATE TABLE a_unique_index_composite (
UNIQUE (a_key1, a_key2)
);
+-- table a_partition_table
+CREATE TABLE a_partition_table (
+ a_key1 INTEGER,
+ a_key2 TIMESTAMP,
+ CONSTRAINT a_partition_table_pkey PRIMARY KEY (a_key1,a_key2)
+) PARTITION BY RANGE (a_key2);
+
-- enum type
CREATE TYPE a_enum AS ENUM (
'ONE',
diff --git a/cmd/schema.go b/cmd/schema.go
index 6eb9c409..99da2706 100644
--- a/cmd/schema.go
+++ b/cmd/schema.go
@@ -200,9 +200,13 @@ func LoadTables(ctx context.Context, args *Args, typ string) ([]xo.Table, error)
}
// create table
t := &xo.Table{
- Type: typ,
- Name: table.TableName,
- Manual: true,
+ Type: typ,
+ Name: table.TableName,
+ Manual: true,
+ Partition: xo.Partition{
+ Reference: table.PartitionOf,
+ Definition: table.PartitionDef,
+ },
Definition: strings.TrimSpace(table.ViewDef),
}
// process columns
diff --git a/gen.sh b/gen.sh
index 3835a367..6faf7a6f 100755
--- a/gen.sh
+++ b/gen.sh
@@ -152,21 +152,27 @@ $XOBIN query $PGDB -M -B -2 -T Table -F PostgresTables --type-comment "$COMMENT"
SELECT
(CASE c.relkind
WHEN 'r' THEN 'table'
+ WHEN 'p' THEN 'table'
WHEN 'v' THEN 'view'
END)::varchar AS type,
c.relname::varchar AS table_name,
false::boolean AS manual_pk,
CASE c.relkind
WHEN 'r' THEN COALESCE(obj_description(c.relname::regclass), '')
+ WHEN 'p' THEN COALESCE(obj_description(c.relname::regclass), '')
WHEN 'v' THEN v.definition
- END AS view_def
+ END AS view_def,
+ (CASE WHEN i.inhparent IS NOT NULL THEN (SELECT pg_class.relname FROM pg_class WHERE pg_class.oid = i.inhparent) ELSE '' END) as partition_of,
+ COALESCE(pg_get_expr(c.relpartbound, c.oid, true), pg_get_partkeydef(c.oid), '') as partition_def
FROM pg_class c
JOIN ONLY pg_namespace n ON n.oid = c.relnamespace
LEFT JOIN pg_views v ON n.nspname = v.schemaname
AND v.viewname = c.relname
+ LEFT JOIN pg_inherits i ON i.inhrelid = c.oid
WHERE n.nspname = %%schema string%%
AND (CASE c.relkind
WHEN 'r' THEN 'table'
+ WHEN 'p' THEN 'table'
WHEN 'v' THEN 'view'
END) = LOWER(%%typ string%%)
ENDSQL
diff --git a/models/table.xo.go b/models/table.xo.go
index 8dd72870..f8278d16 100644
--- a/models/table.xo.go
+++ b/models/table.xo.go
@@ -12,6 +12,8 @@ type Table struct {
TableName string `json:"table_name"` // table_name
ManualPk bool `json:"manual_pk"` // manual_pk
ViewDef string `json:"view_def"` // view_def
+ PartitionOf string `json:"partition_of"` // partition_of
+ PartitionDef string `json:"partition_def"` // partition_def
}
// PostgresTables runs a custom query, returning results as [Table].
@@ -20,21 +22,27 @@ func PostgresTables(ctx context.Context, db DB, schema, typ string) ([]*Table, e
const sqlstr = `SELECT ` +
`(CASE c.relkind ` +
`WHEN 'r' THEN 'table' ` +
+ `WHEN 'p' THEN 'table' ` +
`WHEN 'v' THEN 'view' ` +
`END), ` + // ::varchar AS type
`c.relname, ` + // ::varchar AS table_name
`false, ` + // ::boolean AS manual_pk
`CASE c.relkind ` +
`WHEN 'r' THEN COALESCE(obj_description(c.relname::regclass), '') ` +
+ `WHEN 'p' THEN COALESCE(obj_description(c.relname::regclass), '') ` +
`WHEN 'v' THEN v.definition ` +
- `END AS view_def ` +
+ `END AS view_def, ` +
+ `(CASE WHEN i.inhparent IS NOT NULL THEN (SELECT pg_class.relname FROM pg_class WHERE pg_class.oid = i.inhparent) ELSE '' END) as partition_of, ` +
+ `COALESCE(pg_get_expr(c.relpartbound, c.oid, true), pg_get_partkeydef(c.oid), '') as partition_def ` +
`FROM pg_class c ` +
`JOIN ONLY pg_namespace n ON n.oid = c.relnamespace ` +
`LEFT JOIN pg_views v ON n.nspname = v.schemaname ` +
`AND v.viewname = c.relname ` +
+ `LEFT JOIN pg_inherits i ON i.inhrelid = c.oid ` +
`WHERE n.nspname = $1 ` +
`AND (CASE c.relkind ` +
`WHEN 'r' THEN 'table' ` +
+ `WHEN 'p' THEN 'table' ` +
`WHEN 'v' THEN 'view' ` +
`END) = LOWER($2)`
// run
@@ -49,7 +57,7 @@ func PostgresTables(ctx context.Context, db DB, schema, typ string) ([]*Table, e
for rows.Next() {
var t Table
// scan
- if err := rows.Scan(&t.Type, &t.TableName, &t.ManualPk, &t.ViewDef); err != nil {
+ if err := rows.Scan(&t.Type, &t.TableName, &t.ManualPk, &t.ViewDef, &t.PartitionOf, &t.PartitionDef); err != nil {
return nil, logerror(err)
}
res = append(res, &t)
diff --git a/templates/createdb/xo.xo.sql.tpl b/templates/createdb/xo.xo.sql.tpl
index f92c8b01..669303bd 100644
--- a/templates/createdb/xo.xo.sql.tpl
+++ b/templates/createdb/xo.xo.sql.tpl
@@ -18,6 +18,7 @@ CREATE TYPE {{ esc $e.Name }} AS ENUM (
{{- if $s.Tables }}
{{- range $t := $s.Tables }}
-- table {{ $t.Name }}
+{{- if eq $t.Partition.Reference ""}}
CREATE TABLE {{ esc $t.Name }} (
{{- range $i, $c := $t.Columns }}
{{ coldef $t $c }}{{ comma $i $t.Columns }}
@@ -28,11 +29,24 @@ CREATE TABLE {{ esc $t.Name }} (
{{- range $fk := $t.ForeignKeys -}}{{- if gt (len $fk.Fields) 1 }},
{{ constraint $fk.Name -}} FOREIGN KEY ({{ fields $fk.Fields }}) REFERENCES {{ esc $fk.RefTable }} ({{ fields $fk.RefFields }})
{{- end -}}{{- end }}
-){{ engine }};
+)
+{{- if $t.Partition.Definition }}
+PARTITION BY {{$t.Partition.Definition}}
+{{- end -}}
+{{- else }}
+CREATE TABLE {{ esc $t.Name }} PARTITION OF {{ $t.Partition.Reference }}
+{{$t.Partition.Definition}}
+{{- end -}}
+{{ engine }};
+
{{- if $t.Indexes }}
{{ range $idx := $t.Indexes }}{{ if not (or $idx.IsPrimary $idx.IsUnique) }}
-- index {{ $idx.Name }}
+{{- if $t.Partition.Reference }}
+CREATE INDEX IF NOT EXISTS {{ esc $idx.Name }} ON {{ esc $t.Name }} ({{ fields $idx.Fields }});
+{{ else }}
CREATE INDEX {{ esc $idx.Name }} ON {{ esc $t.Name }} ({{ fields $idx.Fields }});
+{{ end -}}
{{ end -}}{{- end -}}{{- end }}
{{ end -}}
{{- end -}}
diff --git a/templates/go/schema.xo.go.tpl b/templates/go/schema.xo.go.tpl
index a236f1ff..3b99f983 100644
--- a/templates/go/schema.xo.go.tpl
+++ b/templates/go/schema.xo.go.tpl
@@ -222,7 +222,7 @@ func (err ErrInvalid{{ $e.GoName }}) Error() string {
{{ define "typedef" }}
{{- $t := .Data -}}
{{- if $t.Comment -}}
-// {{ $t.Comment | eval $t.GoName }}
+/* {{ $t.Comment | eval $t.GoName }} */
{{- else -}}
// {{ $t.GoName }} represents a row from '{{ schema $t.SQLName }}'.
{{- end }}
diff --git a/types/types.go b/types/types.go
index adc30869..57c80320 100644
--- a/types/types.go
+++ b/types/types.go
@@ -98,6 +98,7 @@ type Table struct {
ForeignKeys []ForeignKey `json:"foreign_keys,omitempty"`
Manual bool `json:"manual,omitempty"`
Definition string `json:"definition,omitempty"` // empty for tables
+ Partition Partition `json:"partition,omitempty"` // empty for views
}
// MarshalYAML satisfies the yaml.Marshaler interface.
@@ -150,6 +151,12 @@ type Type struct {
Enum *Enum `json:"-"`
}
+// Partition holds information about table partition.
+type Partition struct {
+ Reference string `json:"reference,omitempty"`
+ Definition string `json:"definition,omitempty"`
+}
+
// ParseType parses "type[ (precision[,scale])][\[\]]" strings returning the
// parsed precision, scale, and if the type is an array or not.
//