Skip to content

Commit

Permalink
Small refacto
Browse files Browse the repository at this point in the history
  • Loading branch information
loicknuchel committed Sep 27, 2024
1 parent 720f95f commit 84f1382
Show file tree
Hide file tree
Showing 5 changed files with 36 additions and 20 deletions.
2 changes: 1 addition & 1 deletion libs/aml/resources/full.aml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ organizations
identity...profiles
id int pk -- users(id) | one-to-one relation

admins {view, dependsOn: [users]}
admins {view}
id
first_name
last_name
Expand Down
6 changes: 3 additions & 3 deletions libs/aml/resources/full.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
{"name": "item", "attrs": [["item_kind"], ["item_id"]]}
],
"doc": "a table with most options\nlooks quite complex but not intended to be used all together ^^",
"extra": {"color": "#ccc", "tags": ["utils", "owner:infra"], "line": 41, "statement": 6, "alias": "c", "comment": "several additional props"}
"extra": {"line": 41, "statement": 6, "alias": "c", "color": "#ccc", "tags": ["utils", "owner:infra"], "comment": "several additional props"}
},
{
"database": "db1",
Expand Down Expand Up @@ -133,7 +133,7 @@
{"name": "last_name", "type": "unknown"},
{"name": "email", "type": "unknown"}
],
"extra": {"dependsOn": ["users"], "line": 70, "statement": 12}
"extra": {"line": 70, "statement": 12}
},
{
"name": "guests",
Expand Down Expand Up @@ -237,7 +237,7 @@
"types": [
{"name": "comment_item", "values": ["User", "Post"], "extra": {"line": 46, "statement": 6, "inline": true}},
{"name": "slug", "doc": "anonymous type", "extra": {"line": 78, "statement": 14}},
{"name": "uid", "alias": "int", "extra": {"tags": ["generic"], "line": 79, "statement": 15, "comment": "alias type"}},
{"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"}}
Expand Down
19 changes: 12 additions & 7 deletions libs/aml/src/amlBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,18 +207,15 @@ type InlineType = {type: Type, position: TokenPosition}

function buildAttribute(namespace: Namespace, statement: number, a: AttributeAstNested, entity: EntityRef): { attribute: Attribute, relations: InlineRelation[], types: InlineType[] } {
const {entity: _, ...entityNamespace} = entity
const typeExt = a.enumValues && a.enumValues.length <= 2 && a.enumValues.every(v => v.token === 'Integer') ? '(' + a.enumValues.map(stringifyAttrValue).join(',') + ')' : ''
const enumType: InlineType[] = a.type && a.enumValues && !typeExt ? [{
type: {...entityNamespace, name: a.type.value, values: a.enumValues.map(stringifyAttrValue), extra: {line: a.enumValues[0].position.start.line, statement, inline: true}},
position: mergePositions(a.enumValues)
}] : []
const numType = a.enumValues && a.enumValues.length <= 2 && a.enumValues.every(v => v.token === 'Integer') ? '(' + a.enumValues.map(stringifyAttrValue).join(',') + ')' : '' // types with num parameter (varchar(10), decimal(2,3)...)
const enumType: InlineType[] = buildTypeInline(entityNamespace, statement, a, numType)
const relation: InlineRelation[] = a.relation ? [{namespace, statement, entity, attrs: [a.path.map(p => p.value)], ref: a.relation}] : []
const validAttrs = (a.attrs || []).filter(aa => !aa.path.some(p => p === undefined)) // `path` can be `[undefined]` on invalid input :/
const nested = validAttrs.map(aa => buildAttribute(namespace, statement, aa, entity))
return {
attribute: removeEmpty({
name: a.path[a.path.length - 1].value,
type: buildAttributeType(a, typeExt),
type: buildAttributeType(a, numType),
null: a.nullable ? true : undefined,
gen: undefined,
default: a.defaultValue ? buildAttrValue(a.defaultValue) : undefined,
Expand Down Expand Up @@ -280,7 +277,8 @@ function buildRelationAttribute(entities: Entity[], aliases: Record<string, Enti
}

function buildRelation(entities: Entity[], aliases: Record<string, EntityRef>, statement: number, line: number, kind: RelationKindAst | undefined, srcEntity: EntityRef, srcAlias: string | undefined, srcAttrs: AttributePath[], ref: AttributeRefCompositeAst, polymorphic: RelationPolymorphicAst | undefined, extra: ExtraAst | undefined, inline: boolean): Relation | undefined {
if (!ref || !ref.entity.value || !ref.attrs || ref.attrs.some(a => a.value === undefined)) return undefined // `ref` can be undefined or with empty entity or undefined attrs on invalid input :/ TODO: report an error instead of just ignoring?
// TODO: report an error instead of just ignoring?
if (!ref || !ref.entity.value || !ref.attrs || ref.attrs.some(a => a.value === undefined)) return undefined // `ref` can be undefined or with empty entity or undefined attrs on invalid input :/
const [refEntity, refAlias] = buildEntityRef(ref, {}, aliases) // current namespace not used for relation ref, good idea???
const refAttrs: AttributePath[] = ref.attrs.length > 0 ? ref.attrs.map(buildAttrPath) : entities.find(e => entityRefSame(entityToRef(e), refEntity))?.pk?.attrs || [['unknown']]
const natural = ref.attrs.length === 0 ? (srcAttrs.length === 0 ? 'both' : 'ref') : (srcAttrs.length === 0 ? 'src' : undefined)
Expand Down Expand Up @@ -320,6 +318,13 @@ function buildAttrPath(a: AttributePathAst): AttributePath {
return [a.value].concat(a.path?.map(p => p.value) || [])
}

function buildTypeInline(namespace: Namespace, statement: number, a: AttributeAstNested, numType: string): InlineType[] {
return a.type && a.enumValues && !numType ? [{
type: {...namespace, name: a.type.value, values: a.enumValues.map(stringifyAttrValue), extra: {line: a.enumValues[0].position.start.line, statement, inline: true}},
position: mergePositions(a.enumValues)
}] : []
}

function buildType(namespace: Namespace, statement: number, t: TypeStatement): Type {
const astNamespace = removeUndefined({schema: t.schema?.value, catalog: t.catalog?.value, database: t.database?.value})
const typeNamespace = {...namespace, ...astNamespace}
Expand Down
18 changes: 13 additions & 5 deletions libs/aml/src/amlGenerator.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
import {isNever, partition} from "@azimutt/utils";
import {
Attribute,
attributeExtraKeys,
attributeExtraProps,
AttributePath,
attributePathSame,
AttributeValue,
Check,
Database,
Entity,
entityExtraKeys,
entityExtraProps,
EntityRef,
entityRefSame,
entityToRef,
Extra,
legacyColumnTypeUnknown,
Namespace,
Relation,
Type
relationExtraKeys,
relationExtraProps,
Type,
typeExtraKeys,
typeExtraProps
} from "@azimutt/models";
import {amlKeywords, PropertyValue} from "./amlAst";

Expand Down Expand Up @@ -67,7 +75,7 @@ function getCurrentNamespace(database: Database, s: {extra?: {line?: number}}):
export function genEntity(e: Entity, namespace: Namespace, relations: Relation[], types: Type[], legacy: boolean): string {
const legacyView = e.kind === 'view' && legacy ? '*' : ''
const alias = e.extra?.alias && !legacy ? ' as ' + genIdentifier(e.extra.alias) : ''
const props = !legacy ? genProperties(e.extra, e.kind === 'view' && !legacy ? {view: e.def?.replaceAll(/\n/g, '\\n')} : {}, ['line', 'statement', 'alias', 'comment']) : ''
const props = !legacy ? genProperties(e.extra, e.kind === 'view' && !legacy ? {view: e.def?.replaceAll(/\n/g, '\\n')} : {}, entityExtraKeys.filter(k => !entityExtraProps.includes(k))) : ''
const entity = `${genName(e, namespace, legacy)}${legacyView}${alias}${props}${genDoc(e.doc, legacy)}${genComment(e.extra?.comment)}\n`
return entity + (e.attrs ? e.attrs.map(a => genAttribute(a, e, namespace, relations.filter(r => r.attrs[0].src[0] === a.name), types, legacy)).join('') : '')
}
Expand All @@ -89,7 +97,7 @@ function genAttributeInner(a: Attribute, e: Entity, namespace: Namespace, relati
.join('')
const checks = (e.checks || []).filter(c => c.attrs.some(attr => attributePathSame(attr, path))).map(c => ' ' + genCheck(c, path, legacy)).join('')
const rel = relations.map(r => ' ' + genRelationTarget(r, namespace, false, legacy)).join('')
const props = !legacy ? genProperties(a.extra, {}, ['comment']) : ''
const props = !legacy ? genProperties(a.extra, {}, attributeExtraKeys.filter(k => !attributeExtraProps.includes(k))) : ''
return `${genIdentifier(a.name)}${genAttributeType(a, types)}${a.null ? ' nullable' : ''}${pk}${indexes}${checks}${rel}${props}${genDoc(a.doc, legacy, indent)}${genComment(a.extra?.comment)}`
}
function genCheck(c: Check, path: AttributePath, legacy: boolean) {
Expand Down Expand Up @@ -126,7 +134,7 @@ function genRelation(r: Relation, namespace: Namespace, legacy: boolean): string
if (legacy && r.attrs.length > 1) return r.attrs.map(attr => genRelation({...r, attrs: [attr]}, namespace, legacy)).join('') // in v1 composite relations are defined as several relations
if (legacy && (r.extra?.natural === 'both' || r.extra?.natural === 'src')) return '' // v1 doesn't support src natural relation
const srcNatural: boolean = !r.extra?.inline && (r.extra?.natural === 'src' || r.extra?.natural === 'both')
const props = !legacy ? genProperties(r.extra, {}, ['line', 'statement', 'inline', 'natural', 'srcAlias', 'refAlias', 'comment']) : ''
const props = !legacy ? genProperties(r.extra, {}, relationExtraKeys.filter(k => !relationExtraProps.includes(k))) : ''
return `${legacy ? 'fk' : 'rel'} ${genAttributeRef(r.src, r.attrs.map(a => a.src), namespace, srcNatural, r.extra?.srcAlias, legacy)} ${genRelationTarget(r, namespace, true, legacy)}${props}${genDoc(r.doc, legacy)}${genComment(r.extra?.comment)}\n`
}

Expand All @@ -150,7 +158,7 @@ function genAttributePath(p: AttributePath, legacy: boolean): string {

function genType(t: Type, namespace: Namespace, legacy: boolean): string {
if (legacy) return '' // no type in v1
const props = genProperties(t.extra, {}, ['line', 'statement', 'comment'])
const props = genProperties(t.extra, {}, typeExtraKeys.filter(k => !typeExtraProps.includes(k)))
return `type ${genName(t, namespace, legacy)}${genTypeContent(t, namespace, legacy)}${props}${genDoc(t.doc, legacy)}${genComment(t.extra?.comment)}\n`
}

Expand Down
11 changes: 7 additions & 4 deletions libs/models/src/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {DateTime, Millis} from "./common";
// - function to diff two Database
// - function to merge two Database
// - convert Database to Project and the reverse
// - parseAttributeType(AttributeType): AttributeTypeParsed

export const DatabaseName = z.string()
export type DatabaseName = z.infer<typeof DatabaseName>
Expand Down Expand Up @@ -155,6 +154,7 @@ export const AttributeExtra = Extra.and(z.object({
}))
export type AttributeExtra = z.infer<typeof AttributeExtra>
export const attributeExtraKeys = ['line', 'statement', 'autoIncrement', 'hidden', 'tags', 'comment']
export const attributeExtraProps = ['autoIncrement', 'hidden', 'tags'] // extra keys manually set in properties

export const Attribute: z.ZodType<Attribute> = z.object({
name: AttributeName,
Expand Down Expand Up @@ -205,13 +205,13 @@ export const EntityExtra = Extra.and(z.object({
statement: z.number().optional(),
alias: z.string().optional(),
view: z.string().optional(), // query definition of the view
dependsOn: z.union([EntityId, AttributeId]).array().optional(), // for views, to know used entities/attributes
color: z.string().optional(),
tags: z.string().array().optional(),
comment: z.string().optional(), // if there is a comment in the entity line
}))
export type EntityExtra = z.infer<typeof EntityExtra>
export const entityExtraKeys = ['line', 'statement', 'alias', 'view', 'dependsOn', 'color', 'tags', 'comment']
export const entityExtraKeys = ['line', 'statement', 'alias', 'view', 'color', 'tags', 'comment']
export const entityExtraProps = ['view', 'color', 'tags'] // extra keys manually set in properties

export const Entity = Namespace.extend({
name: EntityName,
Expand Down Expand Up @@ -247,10 +247,11 @@ export const RelationExtra = Extra.and(z.object({
}))
export type RelationExtra = z.infer<typeof RelationExtra>
export const relationExtraKeys = ['line', 'statement', 'inline', 'natural', 'onUpdate', 'onDelete', 'srcAlias', 'refAlias', 'tags', 'comment']
export const relationExtraProps = ['onUpdate', 'onDelete', 'tags'] // extra keys manually set in properties

export const Relation = z.object({
name: ConstraintName.optional(),
kind: RelationKind.optional(), // 'many-to-one' when not specified TODO: split in srcCardinality & refCardinality (or add cardinality in src & ref)
kind: RelationKind.optional(), // 'many-to-one' when not specified
origin: z.enum(['fk', 'infer-name', 'infer-similar', 'infer-query', 'user']).optional(), // 'fk' when not specified
src: EntityRef,
ref: EntityRef,
Expand All @@ -269,6 +270,8 @@ export const TypeExtra = Extra.and(z.object({
comment: z.string().optional(), // if there is a comment in the type line
}))
export type TypeExtra = z.infer<typeof TypeExtra>
export const typeExtraKeys = ['line', 'statement', 'inline', 'tags', 'comment']
export const typeExtraProps = ['tags'] // extra keys manually set in properties

export const Type = Namespace.extend({
name: TypeName,
Expand Down

0 comments on commit 84f1382

Please sign in to comment.