From 85313e9b7452c0c3f5d807a5d36e0e17f5cacd4d Mon Sep 17 00:00:00 2001 From: Constantine Molchanov Date: Tue, 30 Jan 2024 15:52:51 +0400 Subject: [PATCH] Add the ability to set custom schema name for Postres. --- src/norm/model.nim | 26 +++++++++++++++++++++++--- src/norm/postgres.nim | 14 +++++++++++++- src/norm/pragmas.nim | 3 +++ src/norm/sqlite.nim | 5 +++++ tests/common/tmodel.nim | 1 + tests/models.nim | 6 ++++++ tests/postgres/ttables.nim | 17 +++++++++++++++++ 7 files changed, 68 insertions(+), 4 deletions(-) diff --git a/src/norm/model.nim b/src/norm/model.nim index e3aaffd5..60053012 100644 --- a/src/norm/model.nim +++ b/src/norm/model.nim @@ -36,13 +36,33 @@ func model*[T](val: T): Option[Model] = none Model +func schema*(T: typedesc[Model]): Option[string] = + ##[ Get schema name for `Model <#Model>`_, which is the value of `schemaName `_ pragma. + + `none(string)` means default schema. + + Ignored in SQLite. + ]## + + when T.hasCustomPragma(schemaName): + some '"' & T.getCustomPragmaVal(schemaName) & '"' + else: + none(string) + func table*(T: typedesc[Model]): string = - ## Get table name for `Model <#Model>`_, which is the type name in single quotes. + ##[ Get table name for `Model <#Model>`_, + which is the value of `tableName `_ pragma or the type name in double quotes. + + If `schemaName`_ is set, it's prepended to the table name. + ]## + + if T.schema.isSome: + result &= get(T.schema) & '.' when T.hasCustomPragma(tableName): - '"' & T.getCustomPragmaVal(tableName) & '"' + result &= '"' & T.getCustomPragmaVal(tableName) & '"' else: - '"' & $T & '"' + result &= '"' & $T & '"' func col*(T: typedesc[Model], fld: string): string = ## Get column name for a `Model`_ field, which is just the field name. diff --git a/src/norm/postgres.nim b/src/norm/postgres.nim index f70a6ecb..2e16cfe0 100644 --- a/src/norm/postgres.nim +++ b/src/norm/postgres.nim @@ -74,8 +74,18 @@ proc dropDb* = # Table manipulation +proc createSchema*[T: Model](dbConn; obj: T) = + ## Create schema for `Model `_. + + if T.schema.isSome: + let qry = "CREATE SCHEMA IF NOT EXISTS $#" % get(T.schema) + + log(qry) + + dbConn.exec(sql qry) + proc createTables*[T: Model](dbConn; obj: T) = - ## Create tables for `Model `_ and its `Model`_ fields. + ## Create tables for `Model`_ and its `Model`_ fields. for fld, val in obj[].fieldPairs: if val.model.isSome: @@ -138,6 +148,8 @@ proc createTables*[T: Model](dbConn; obj: T) = colGroups.add colShmParts.join(" ") + dbConn.createSchema(obj) + let uniqueGroups = if len(uniqueGroupCols) > 0: @["UNIQUE($#)" % uniqueGroupCols.join(", ")] else: @[] qry = "CREATE TABLE IF NOT EXISTS $#($#)" % [T.table, (colGroups & fkGroups & uniqueGroups).join(", ")] diff --git a/src/norm/pragmas.nim b/src/norm/pragmas.nim index 337e44fe..1b861a5f 100644 --- a/src/norm/pragmas.nim +++ b/src/norm/pragmas.nim @@ -34,6 +34,9 @@ template fk*(val: typed) {.pragma.} template onDelete*(val: string) {.pragma.} ## Add ``ON DELETE {val}`` constraint to the column. +template schemaName*(val: string) {.pragma.} + ## Custom schema name for a model. + template tableName*(val: string) {.pragma.} ## Custom table name for a model. diff --git a/src/norm/sqlite.nim b/src/norm/sqlite.nim index 2286e719..b89c737d 100644 --- a/src/norm/sqlite.nim +++ b/src/norm/sqlite.nim @@ -69,6 +69,11 @@ proc dropDb* = # Table manipulation +proc createSchema*[T: Model](dbConn; obj: T) = + ## Dummy proc for API consistency. + + discard + proc createTables*[T: Model](dbConn; obj: T) = ## Create tables for `Model `_ and its `Model`_ fields. diff --git a/tests/common/tmodel.nim b/tests/common/tmodel.nim index f72e806f..fcd098fd 100644 --- a/tests/common/tmodel.nim +++ b/tests/common/tmodel.nim @@ -9,6 +9,7 @@ suite "Getting table and columns from Model": test "Table": check Person.table == """"Person"""" check Table.table == """"FurnitureTable"""" + check FurnitureTable.table == """"Furniture"."FurnitureTable"""" test "Columns": let diff --git a/tests/models.nim b/tests/models.nim index ffdc7b20..0374e274 100644 --- a/tests/models.nim +++ b/tests/models.nim @@ -76,6 +76,9 @@ type Table* {.tableName: "FurnitureTable".} = ref object of Model legCount*: Positive + FurnitureTable* {.schemaName: "Furniture", tableName: "FurnitureTable".} = ref object of Model + legCount*: Positive + SelfRef* = ref object of Model parent* {.fk: SelfRef.}: Option[int64] @@ -237,6 +240,9 @@ func `===`*(a, b: String): bool = func newTable*(legCount: Positive = 4): Table = Table(legCount: legCount) +func newFurnitureTable*(legCount: Positive = 4): FurnitureTable = + FurnitureTable(legCount: legCount) + func newPetSpecies*: PetSpecies = PetSpecies(species: "") func newPersonName*: PersonName = PersonName(name: "") diff --git a/tests/postgres/ttables.nim b/tests/postgres/ttables.nim index 7bd8fbb6..577c96d4 100644 --- a/tests/postgres/ttables.nim +++ b/tests/postgres/ttables.nim @@ -59,6 +59,23 @@ suite "Table creation": @[?"legcount", ?dftDbInt] ] + test "Create table with custom schema": + let furnitureTable = newFurnitureTable() + + dbConn.createTables(furnitureTable) + + let + qry = sql """SELECT column_name::text, data_type::text + FROM information_schema.columns + WHERE table_schema = $1 AND table_name = $2 + ORDER BY column_name""" + dftDbInt = if high(int) == high(int64): "bigint" else: "integer" + + check dbConn.getAllRows(qry, "Furniture", "FurnitureTable") == @[ + @[?"id", ?"bigint"], + @[?"legcount", ?dftDbInt] + ] + test "Create tables": let toy = newToy(123.45)