diff --git a/libs/aml/resources/full.aml b/libs/aml/resources/full.aml index ceff76306..f9a682d6f 100644 --- a/libs/aml/resources/full.aml +++ b/libs/aml/resources/full.aml @@ -79,7 +79,7 @@ type slug | anonymous type type uid int {tags: [generic]} # alias type type cms.post_status (draft, published, archived) # enum type type position {x int, y int} # struct type -type box `(INTERNALLENGTH = 16, INPUT = lower, OUTPUT = lower)` # custom type +type box `(INPUT = lower, OUTPUT = lower, INTERNALLENGTH = 16)` # custom type namespace social. diff --git a/libs/aml/resources/full.json b/libs/aml/resources/full.json index afc941a27..39a9e049a 100644 --- a/libs/aml/resources/full.json +++ b/libs/aml/resources/full.json @@ -224,7 +224,7 @@ {"name": "uid", "alias": "int", "extra": {"line": 79, "statement": 15, "tags": ["generic"], "comment": "alias type"}}, {"schema": "cms", "name": "post_status", "values": ["draft", "published", "archived"], "extra": {"line": 80, "statement": 16, "comment": "enum type"}}, {"name": "position", "attrs": [{"name": "x", "type": "int"}, {"name": "y", "type": "int"}], "extra": {"line": 81, "statement": 17, "comment": "struct type"}}, - {"name": "box", "definition": "(INTERNALLENGTH = 16, INPUT = lower, OUTPUT = lower)", "extra": {"line": 82, "statement": 18, "comment": "custom type"}} + {"name": "box", "definition": "(INPUT = lower, OUTPUT = lower, INTERNALLENGTH = 16)", "extra": {"line": 82, "statement": 18, "comment": "custom type"}} ], "extra": { "comments": [{"line": 1, "comment": ""}, {"line": 2, "comment": "Full Schema AML"}, {"line": 3, "comment": ""}], diff --git a/libs/aml/resources/full.md b/libs/aml/resources/full.md index 3d63d6ca5..d938d54ca 100644 --- a/libs/aml/resources/full.md +++ b/libs/aml/resources/full.md @@ -165,7 +165,7 @@ STRUCT: ### box -EXPRESSION: (INTERNALLENGTH = 16, INPUT = lower, OUTPUT = lower) +EXPRESSION: (INPUT = lower, OUTPUT = lower, INTERNALLENGTH = 16) ## Diagram diff --git a/libs/parser-sql/resources/full.postgres.sql b/libs/parser-sql/resources/full.postgres.sql index b90ad32ce..617584e99 100644 --- a/libs/parser-sql/resources/full.postgres.sql +++ b/libs/parser-sql/resources/full.postgres.sql @@ -16,7 +16,7 @@ COMMENT ON TYPE slug IS 'anonymous type'; -- CREATE TYPE uid AS int; -- type alias not supported on PostgreSQL CREATE TYPE cms.post_status AS ENUM ('draft', 'published', 'archived'); CREATE TYPE position AS (x int, y int); -CREATE TYPE box (INTERNALLENGTH = 16, INPUT = lower, OUTPUT = lower); +CREATE TYPE box (INPUT = lower, OUTPUT = lower, INTERNALLENGTH = 16); -- -- Full Schema AML diff --git a/libs/parser-sql/src/postgresAst.ts b/libs/parser-sql/src/postgresAst.ts index 8e244fe95..540d6586d 100644 --- a/libs/parser-sql/src/postgresAst.ts +++ b/libs/parser-sql/src/postgresAst.ts @@ -16,7 +16,7 @@ export type CommentStatementAst = { kind: 'Comment', object: { kind: CommentObje export type CreateExtensionStatementAst = { kind: 'CreateExtension', ifNotExists?: TokenInfo, name: IdentifierAst, with?: TokenInfo, schema?: {name: IdentifierAst} & TokenInfo, version?: {number: StringAst | IdentifierAst} & TokenInfo, cascade?: TokenInfo } & TokenInfo export type CreateIndexStatementAst = { kind: 'CreateIndex', unique?: TokenInfo, concurrently?: TokenInfo, ifNotExists?: TokenInfo, index?: IdentifierAst, only?: TokenInfo, schema?: IdentifierAst, table: IdentifierAst, using?: {method: IdentifierAst} & TokenInfo, columns: IndexColumnAst[], include?: {columns: IdentifierAst[]} & TokenInfo, where?: {predicate: ExpressionAst} & TokenInfo } & TokenInfo export type CreateTableStatementAst = { kind: 'CreateTable', schema?: IdentifierAst, table: IdentifierAst, columns: TableColumnAst[], constraints?: TableConstraintAst[] } & TokenInfo -export type CreateTypeStatementAst = { kind: 'CreateType', schema?: IdentifierAst, type: IdentifierAst, struct?: {attrs: TypeColumnAst[]} & TokenInfo, enum?: {values: StringAst[]} & TokenInfo } & TokenInfo +export type CreateTypeStatementAst = { kind: 'CreateType', schema?: IdentifierAst, type: IdentifierAst, struct?: {attrs: TypeColumnAst[]} & TokenInfo, enum?: {values: StringAst[]} & TokenInfo, base?: {name: IdentifierAst, value: ExpressionAst}[] } & TokenInfo export type DropStatementAst = { kind: 'Drop', object: { kind: DropObject } & TokenInfo, entities: TableRefAst[], concurrently?: TokenInfo, ifExists?: TokenInfo, mode?: { kind: DropMode } & TokenInfo } & TokenInfo export type InsertIntoStatementAst = { kind: 'InsertInto', schema?: IdentifierAst, table: IdentifierAst, columns?: IdentifierAst[], values: (ExpressionAst | { kind: 'Default' } & TokenInfo)[][], returning?: SelectClauseAst } & TokenInfo export type SelectStatementAst = { kind: 'Select', select: SelectClauseAst, from?: FromClauseAst, where?: WhereClauseAst } & TokenInfo diff --git a/libs/parser-sql/src/postgresParser.test.ts b/libs/parser-sql/src/postgresParser.test.ts index 966f2f9eb..ee27944d8 100644 --- a/libs/parser-sql/src/postgresParser.test.ts +++ b/libs/parser-sql/src/postgresParser.test.ts @@ -240,6 +240,19 @@ describe('postgresParser', () => { ...token(0, 56) }]}}) }) + // TODO: range + test('base', () => { + expect(parsePostgresAst("CREATE TYPE box (INPUT = my_box_in_function, OUTPUT = my_box_out_function, INTERNALLENGTH = 16);")).toEqual({result: {statements: [{ + kind: 'CreateType', + type: identifier('box', 12, 14), + base: [ + {name: identifier('INPUT', 17, 21), value: {column: identifier('my_box_in_function', 25, 42)}}, // FIXME: expressions should be different here (identifier instead of column) + {name: identifier('OUTPUT', 45, 50), value: {column: identifier('my_box_out_function', 54, 72)}}, + {name: identifier('INTERNALLENGTH', 75, 88), value: integer(16, 92, 93)}, + ], + ...token(0, 95) + }]}}) + }) }) describe('dropStatement', () => { test('simplest', () => { diff --git a/libs/parser-sql/src/postgresParser.ts b/libs/parser-sql/src/postgresParser.ts index b861835a1..b464d07c5 100644 --- a/libs/parser-sql/src/postgresParser.ts +++ b/libs/parser-sql/src/postgresParser.ts @@ -366,32 +366,12 @@ class PostgresParser extends EmbeddedActionsParser { const start = $.CONSUME(Create) $.CONSUME(Type) const table = $.SUBRULE($.tableRefRule) - const content = $.OPTION(() => { - const as = $.CONSUME(As) - return $.OR([ - {ALT: () => { - $.CONSUME(ParenLeft) - const attrs: TypeColumnAst[] = [] - $.AT_LEAST_ONE_SEP({SEP: Comma, DEF: () => attrs.push(removeUndefined({ - name: $.SUBRULE($.identifierRule), - type: $.SUBRULE($.columnTypeRule), - collation: $.OPTION2(() => ({...tokenInfo($.CONSUME(Collate)), name: $.SUBRULE2($.identifierRule)})) - }))}) - $.CONSUME(ParenRight) - return {struct: {...tokenInfo(as), attrs: attrs.filter(isNotUndefined)}} - }}, - {ALT: () => { - const token = tokenInfo2(as, $.CONSUME(Enum)) - $.CONSUME2(ParenLeft) - const values: StringAst[] = [] - $.AT_LEAST_ONE_SEP2({SEP: Comma, DEF: () => values.push($.SUBRULE($.stringRule))}) - $.CONSUME2(ParenRight) - return {enum: {...token, values: values.filter(isNotUndefined)}} - }}, - // TODO: RANGE - // TODO: function - ]) - }) + const content = $.OPTION(() => $.OR([ + {ALT: () => ({struct: {...tokenInfo($.CONSUME(As)), attrs: $.SUBRULE(createTypeStructAttrs)}})}, + {ALT: () => ({enum: {...tokenInfo2($.CONSUME2(As), $.CONSUME(Enum)), values: $.SUBRULE(createTypeEnumValues)}})}, + // TODO: RANGE + {ALT: () => ({base: $.SUBRULE(createTypeBase)})} + ])) const end = $.CONSUME(Semicolon) return removeEmpty({kind: 'CreateType' as const, schema: table.schema, type: table.table, ...content, ...tokenInfo2(start, end)}) }) @@ -621,6 +601,37 @@ class PostgresParser extends EmbeddedActionsParser { return removeUndefined({kind: 'ForeignKey' as const, ...tokenInfo(token), columns, ref, onUpdate, onDelete, constraint}) }) + const createTypeStructAttrs = $.RULE<() => TypeColumnAst[]>('createTypeStructAttrs', () => { + $.CONSUME(ParenLeft) + const attrs: TypeColumnAst[] = [] + $.AT_LEAST_ONE_SEP({SEP: Comma, DEF: () => attrs.push(removeUndefined({ + name: $.SUBRULE($.identifierRule), + type: $.SUBRULE($.columnTypeRule), + collation: $.OPTION(() => ({...tokenInfo($.CONSUME(Collate)), name: $.SUBRULE2($.identifierRule)})) + }))}) + $.CONSUME(ParenRight) + return attrs.filter(isNotUndefined) + }) + const createTypeEnumValues = $.RULE<() => StringAst[]>('createTypeEnumValues', () => { + $.CONSUME(ParenLeft) + const values: StringAst[] = [] + $.AT_LEAST_ONE_SEP({SEP: Comma, DEF: () => values.push($.SUBRULE($.stringRule))}) + $.CONSUME(ParenRight) + return values + }) + const createTypeBase = $.RULE<() => {name: IdentifierAst, value: ExpressionAst}[]>('createTypeBase', () => { + $.CONSUME(ParenLeft) + const params: {name: IdentifierAst, value: ExpressionAst}[] = [] + $.AT_LEAST_ONE_SEP({SEP: Comma, DEF: () => { + const name = $.SUBRULE($.identifierRule) + $.CONSUME(Equal) + const value = $.SUBRULE($.expressionRule) + params.push({name, value}) + }}) + $.CONSUME(ParenRight) + return params + }) + const constraintNameRule = $.RULE<() => ConstraintNameAst>('constraintNameRule', () => { const token = $.CONSUME(Constraint) const name = $.SUBRULE($.identifierRule)