diff --git a/docs/reference.md b/docs/reference.md index 06f54422..ed53b107 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -6199,6 +6199,37 @@ Buyer.select +## Schema +Additional tests to ensure schema mapping produces valid SQL +### Schema.schema + +If your table belongs to a schema other than the default schema of your database, +you can specify this in your table definition with table.schemaName + +```scala +Invoice.select +``` + + +* + ```sql + SELECT invoice0.id AS id, invoice0.total AS total, invoice0.vendor_name AS vendor_name + FROM otherschema.invoice invoice0 + ``` + + + +* + ```scala + Seq( + Invoice[Sc](id = 1, total = 150.4, vendor_name = "Siemens"), + Invoice[Sc](id = 2, total = 213.3, vendor_name = "Samsung"), + Invoice[Sc](id = 3, total = 407.2, vendor_name = "Shell") + ) + ``` + + + ## SubQuery Queries that explicitly use subqueries (e.g. for `JOIN`s) or require subqueries to preserve the Scala semantics of the various operators ### SubQuery.sortTakeJoin diff --git a/scalasql/query/src/From.scala b/scalasql/query/src/From.scala index ef6aeb40..006b4486 100644 --- a/scalasql/query/src/From.scala +++ b/scalasql/query/src/From.scala @@ -15,7 +15,11 @@ class TableRef(val value: Table.Base) extends From { def fromExprAliases(prevContext: Context): Seq[(Expr.Identity, SqlStr)] = Nil def renderSql(name: SqlStr, prevContext: Context, liveExprs: LiveExprs) = { - SqlStr.raw(prevContext.config.tableNameMapper(Table.name(value))) + sql" " + name + val schemaStr = value.schemaName match { + case "" => "" + case str => s"$str." + } + SqlStr.raw(schemaStr + prevContext.config.tableNameMapper(Table.name(value))) + sql" " + name } } diff --git a/scalasql/query/src/Table.scala b/scalasql/query/src/Table.scala index 21839860..4dd811a0 100644 --- a/scalasql/query/src/Table.scala +++ b/scalasql/query/src/Table.scala @@ -11,6 +11,8 @@ abstract class Table[V[_[_]]]()(implicit name: sourcecode.Name, metadata0: Table protected[scalasql] def tableName = name.value + protected[scalasql] def schemaName = "" + protected implicit def tableSelf: Table[V] = this protected def tableMetadata: Table.Metadata[V] = metadata0 @@ -54,6 +56,7 @@ object Table { * Can be overriden to configure the table names */ protected[scalasql] def tableName: String + protected[scalasql] def schemaName: String protected[scalasql] def tableLabels: Seq[String] /** diff --git a/scalasql/test/resources/customer-data-plus-schema.sql b/scalasql/test/resources/customer-data-plus-schema.sql new file mode 100644 index 00000000..11c4a6ac --- /dev/null +++ b/scalasql/test/resources/customer-data-plus-schema.sql @@ -0,0 +1,32 @@ + +INSERT INTO buyer (name, date_of_birth) VALUES +('James Bond', '2001-02-03'), +('叉烧包', '1923-11-12'), +('Li Haoyi', '1965-08-09'); + +INSERT INTO product (kebab_case_name, name, price) VALUES +('face-mask', 'Face Mask', 8.88), +('guitar', 'Guitar', 300), +('socks', 'Socks', 3.14), +('skate-board', 'Skate Board', 123.45), +('camera', 'Camera', 1000.00), +('cookie', 'Cookie', 0.10); + +INSERT INTO shipping_info (buyer_id, shipping_date) VALUES +(2, '2010-02-03'), +(1, '2012-04-05'), +(2, '2012-05-06'); + +INSERT INTO purchase (shipping_info_id, product_id, count, total) VALUES +(1, 1, 100, 888), +(1, 2, 3, 900), +(1, 3, 5, 15.7), +(2, 4, 4, 493.8), +(2, 5, 10, 10000.00), +(3, 1, 5, 44.4), +(3, 6, 13, 1.30); + +INSERT INTO otherschema.invoice (total, vendor_name) VALUES +(150.4, 'Siemens'), +(213.3, 'Samsung'), +(407.2, 'Shell'); diff --git a/scalasql/test/resources/h2-customer-schema.sql b/scalasql/test/resources/h2-customer-schema.sql index 13642506..2a2ba93c 100644 --- a/scalasql/test/resources/h2-customer-schema.sql +++ b/scalasql/test/resources/h2-customer-schema.sql @@ -7,6 +7,8 @@ DROP TABLE IF EXISTS non_round_trip_types CASCADE; DROP TABLE IF EXISTS opt_cols CASCADE; DROP TABLE IF EXISTS nested CASCADE; DROP TABLE IF EXISTS enclosing CASCADE; +DROP TABLE IF EXISTS invoice CASCADE; +DROP SCHEMA IF EXISTS otherschema CASCADE; CREATE TABLE buyer ( id INTEGER AUTO_INCREMENT PRIMARY KEY, @@ -76,3 +78,11 @@ CREATE TABLE enclosing( foo_id INTEGER, my_boolean BOOLEAN ); + +CREATE SCHEMA otherschema; + +CREATE TABLE otherschema.invoice( + id INTEGER AUTO_INCREMENT PRIMARY KEY, + total DECIMAL(20, 2), + vendor_name VARCHAR(256) +); \ No newline at end of file diff --git a/scalasql/test/resources/postgres-customer-schema.sql b/scalasql/test/resources/postgres-customer-schema.sql index 42169334..dfe625fb 100644 --- a/scalasql/test/resources/postgres-customer-schema.sql +++ b/scalasql/test/resources/postgres-customer-schema.sql @@ -7,7 +7,9 @@ DROP TABLE IF EXISTS non_round_trip_types CASCADE; DROP TABLE IF EXISTS opt_cols CASCADE; DROP TABLE IF EXISTS nested CASCADE; DROP TABLE IF EXISTS enclosing CASCADE; +DROP TABLE IF EXISTS invoice CASCADE; DROP TYPE IF EXISTS my_enum CASCADE; +DROP SCHEMA IF EXISTS otherschema CASCADE; CREATE TABLE buyer ( id SERIAL PRIMARY KEY, @@ -68,8 +70,6 @@ CREATE TABLE opt_cols( my_int2 INTEGER ); - - CREATE TABLE nested( foo_id INTEGER, my_boolean BOOLEAN @@ -80,4 +80,12 @@ CREATE TABLE enclosing( my_string VARCHAR(256), foo_id INTEGER, my_boolean BOOLEAN +); + +CREATE SCHEMA otherschema; + +CREATE TABLE otherschema.invoice( + id SERIAL PRIMARY KEY, + total DECIMAL(20, 2), + vendor_name VARCHAR(256) ); \ No newline at end of file diff --git a/scalasql/test/src/ConcreteTestSuites.scala b/scalasql/test/src/ConcreteTestSuites.scala index b4cff752..8b906f39 100644 --- a/scalasql/test/src/ConcreteTestSuites.scala +++ b/scalasql/test/src/ConcreteTestSuites.scala @@ -28,7 +28,8 @@ import query.{ LateralJoinTests, WindowFunctionTests, GetGeneratedKeysTests, - WithCteTests + WithCteTests, + SchemaTests } import scalasql.dialects.{ MySqlDialectTests, @@ -59,6 +60,7 @@ package postgres { object LateralJoinTests extends LateralJoinTests with PostgresSuite object WindowFunctionTests extends WindowFunctionTests with PostgresSuite object GetGeneratedKeysTests extends GetGeneratedKeysTests with PostgresSuite + object SchemaTests extends SchemaTests with PostgresSuite object SubQueryTests extends SubQueryTests with PostgresSuite object WithCteTests extends WithCteTests with PostgresSuite @@ -103,6 +105,7 @@ package hikari { object LateralJoinTests extends LateralJoinTests with HikariSuite object WindowFunctionTests extends WindowFunctionTests with HikariSuite object GetGeneratedKeysTests extends GetGeneratedKeysTests with HikariSuite + object SchemaTests extends SchemaTests with HikariSuite object SubQueryTests extends SubQueryTests with HikariSuite object WithCteTests extends WithCteTests with HikariSuite @@ -163,6 +166,8 @@ package mysql { object ExprStringOpsTests extends ExprStringOpsTests with MySqlSuite object ExprBlobOpsTests extends ExprBlobOpsTests with MySqlSuite object ExprMathOpsTests extends ExprMathOpsTests with MySqlSuite + // In MySql, schemas are databases and this requires special treatment not yet implemented here + // object SchemaTests extends SchemaTests with MySqlSuite object DataTypesTests extends datatypes.DataTypesTests with MySqlSuite object OptionalTests extends datatypes.OptionalTests with MySqlSuite @@ -208,6 +213,8 @@ package sqlite { object ExprBlobOpsTests extends ExprBlobOpsTests with SqliteSuite // Sqlite doesn't support all these math operations // object ExprMathOpsTests extends ExprMathOpsTests with SqliteSuite + // Sqlite doesn't support schemas + // object SchemaTests extends SchemaTests with SqliteSuite object DataTypesTests extends datatypes.DataTypesTests with SqliteSuite object OptionalTests extends datatypes.OptionalTests with SqliteSuite @@ -240,6 +247,7 @@ package h2 { // object LateralJoinTests extends LateralJoinTests with H2Suite object WindowFunctionTests extends WindowFunctionTests with H2Suite object GetGeneratedKeysTests extends GetGeneratedKeysTests with H2Suite + object SchemaTests extends SchemaTests with H2Suite object SubQueryTests extends SubQueryTests with H2Suite object WithCteTests extends WithCteTests with H2Suite diff --git a/scalasql/test/src/UnitTestData.scala b/scalasql/test/src/UnitTestData.scala index 69d01cb7..6e3b5ed4 100644 --- a/scalasql/test/src/UnitTestData.scala +++ b/scalasql/test/src/UnitTestData.scala @@ -9,6 +9,11 @@ object Product extends Table[Product] case class Buyer[T[_]](id: T[Int], name: T[String], dateOfBirth: T[LocalDate]) object Buyer extends Table[Buyer] +case class Invoice[T[_]](id: T[Int], total: T[Double], vendor_name: T[String]) +object Invoice extends Table[Invoice] { + override def schemaName = "otherschema" +} + case class ShippingInfo[T[_]](id: T[Int], buyerId: T[Int], shippingDate: T[LocalDate]) object ShippingInfo extends Table[ShippingInfo] diff --git a/scalasql/test/src/query/SchemaTests.scala b/scalasql/test/src/query/SchemaTests.scala new file mode 100644 index 00000000..ff0f8000 --- /dev/null +++ b/scalasql/test/src/query/SchemaTests.scala @@ -0,0 +1,33 @@ +package scalasql.query + +import scalasql._ +import sourcecode.Text +import utest._ +import utils.ScalaSqlSuite + +import java.time.LocalDate + +trait SchemaTests extends ScalaSqlSuite { + def description = "Additional tests to ensure schema mapping produces valid SQL" + + def tests = Tests { + test("schema") - checker( + query = Text { + Invoice.select + }, + sql = """ + SELECT invoice0.id AS id, invoice0.total AS total, invoice0.vendor_name AS vendor_name + FROM otherschema.invoice invoice0 + """, + value = Seq( + Invoice[Sc](id = 1, total = 150.4, vendor_name = "Siemens"), + Invoice[Sc](id = 2, total = 213.3, vendor_name = "Samsung"), + Invoice[Sc](id = 3, total = 407.2, vendor_name = "Shell") + ), + docs = """ + If your table belongs to a schema other than the default schema of your database, + you can specify this in your table definition with table.schemaName + """ + ) + } +} diff --git a/scalasql/test/src/utils/ScalaSqlSuite.scala b/scalasql/test/src/utils/ScalaSqlSuite.scala index 9fb9fe01..7f84c2bb 100644 --- a/scalasql/test/src/utils/ScalaSqlSuite.scala +++ b/scalasql/test/src/utils/ScalaSqlSuite.scala @@ -31,7 +31,7 @@ trait H2Suite extends ScalaSqlSuite with H2Dialect { val checker = new TestChecker( scalasql.example.H2Example.h2Client, "h2-customer-schema.sql", - "customer-data.sql", + "customer-data-plus-schema.sql", getClass.getName, suiteLine.value, description @@ -44,7 +44,7 @@ trait PostgresSuite extends ScalaSqlSuite with PostgresDialect { val checker = new TestChecker( scalasql.example.PostgresExample.postgresClient, "postgres-customer-schema.sql", - "customer-data.sql", + "customer-data-plus-schema.sql", getClass.getName, suiteLine.value, description @@ -57,7 +57,7 @@ trait HikariSuite extends ScalaSqlSuite with PostgresDialect { val checker = new TestChecker( scalasql.example.HikariCpExample.hikariClient, "postgres-customer-schema.sql", - "customer-data.sql", + "customer-data-plus-schema.sql", getClass.getName, suiteLine.value, description