Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
d272199
Add MSSQL deps
kiendang Sep 5, 2024
a8ad7ce
Add MSSQL dialect
kiendang Sep 6, 2024
cd2d4da
Add MSSQL Example tests
kiendang Sep 6, 2024
6e69644
Add MSSQL test suite
kiendang Sep 6, 2024
53c23de
Set MSSQL_COLLATION for UTF-8
kiendang Sep 6, 2024
814447a
Use + for MS SQL string concatenation in tests
kiendang Sep 6, 2024
615b24b
Use datalength instead of octet_length for MS SQL in tests
kiendang Sep 6, 2024
c144810
Fix some MS SQL syntaxes in tests
kiendang Sep 7, 2024
bba0ba9
Fix some Math functions for MS SQL
kiendang Sep 8, 2024
148c778
Fix ORDER BY with NULL for MS SQL
kiendang Sep 10, 2024
3015da1
Add more valid results for ORDER BY with NULL tests
kiendang Sep 10, 2024
36d8da3
Fix LIMIT and OFFSET for MS SQL
kiendang Sep 10, 2024
396313c
Enable .take without .drop for MS SQL
kiendang Sep 10, 2024
a55a54a
Fix tests involving .take and .drop for MS SQL
kiendang Sep 10, 2024
f079c63
Fix MS SQL numeric ops
kiendang Sep 10, 2024
8be2842
Lint
kiendang Sep 12, 2024
e8dbb48
Wait for mssql container startup to complete
kiendang Sep 12, 2024
c07da40
Fix wrongly edited test case
kiendang Sep 12, 2024
b263ef8
Use a more idiomatic way of waiting for a log output message
kiendang Oct 18, 2024
1763930
Fix some MSSQL specific type casts
kiendang Oct 18, 2024
7bdf159
Add another valid expected output to test case
kiendang Oct 18, 2024
41a6bfb
Exclude EXCLUDE test from MSSQL tests for window functions
kiendang Oct 18, 2024
045f5b4
Fix MSSQL ORDER BY in test
kiendang Oct 18, 2024
30b83a8
Update due to internal API change
kiendang Dec 3, 2024
ec68b6e
fix isEmpty/nonEmpty
jtjeferreira Dec 23, 2024
4255266
fix like, startsWith, endsWith, contains
jtjeferreira Dec 23, 2024
62dc6aa
fix like, startsWith, endsWith, contains but for binary blobs by cast…
jtjeferreira Dec 23, 2024
8832f95
fix WindowFunctionTests by ignoring not supported functions (nthValue…
jtjeferreira Dec 23, 2024
fb4ccb4
fix "Cannot insert explicit value for identity column in table 'buyer…
jtjeferreira Jan 3, 2025
4834410
rework Expr[Boolean] evaluation
jtjeferreira Jan 3, 2025
cd74c5e
fix DbApiTests
jtjeferreira Jan 3, 2025
2ea2b42
ignore ExprBooleanOpsTests for mssql
jtjeferreira Jan 3, 2025
4dbffac
fix JoinTest except JoinTests.leftJoinExpr
jtjeferreira Jan 4, 2025
0340134
fix ExprNumericOpsTests
jtjeferreira Jan 4, 2025
2de28c8
fix SubQueryTests
jtjeferreira Jan 4, 2025
abf0d7b
fix DeleteTests and ExprAggOpsTests (except mkString.sep)
jtjeferreira Jan 4, 2025
6510370
unrelated: rename DbAggOpsTests.scala to ExprAggOpsTests
jtjeferreira Jan 4, 2025
a56a249
a different fix to DeleteTests by improving the where clause in a DEL…
jtjeferreira Jan 4, 2025
ebcdee7
fix TransactionTests (with a TODO)
jtjeferreira Jan 4, 2025
cbcf9d3
ignore ValuesTests for mssql
jtjeferreira Jan 4, 2025
bf92ab4
add MsSqlEnumType
jtjeferreira Jan 4, 2025
1d97d81
fix DataTypesTests.constant
jtjeferreira Jan 4, 2025
f7867a7
fix scalasql.mssql.UpdateTests.dynamic by setting a case-sensitive co…
jtjeferreira Jan 8, 2025
95392e9
ignore scalasql.mssql.ExprSeqOpsTests.mkString.sep test
jtjeferreira Jan 8, 2025
5dcc347
ignore scalasql.mssql.SelectTests.containsMultiple test
jtjeferreira Jan 8, 2025
a797159
simplify JoinTests.leftJoinExpr so it does not use nullsFirst, becaus…
jtjeferreira Jan 8, 2025
2b9da8c
ignore scalasql.mssql.WithCteTests.subquery test
jtjeferreira Jan 8, 2025
3c58e40
fix scalasql.mssql.SchemaTests
jtjeferreira Jan 8, 2025
fcc3ecb
scalafmt
jtjeferreira Jan 8, 2025
f79afc7
scalafix
jtjeferreira Jan 8, 2025
579f4d7
fix scalasql.mssql.OptionalTests.sorting.roundTripOptionalValues
jtjeferreira Jan 8, 2025
424982b
use DATETIME2 in `CREATE TABLE data_types` as that aligns with the Ty…
jtjeferreira Jan 8, 2025
30a0a8b
fix scalasql.mssql.DataTypesTests.nonRoundTrip
jtjeferreira Jan 8, 2025
10c97c5
fix a few TypeMappers overrides (it would only matter if dialectCastP…
jtjeferreira Jan 8, 2025
8de9dd2
swallow releaseSavepoint exception
jtjeferreira Jan 14, 2025
266c7bb
use myBoolean false instead of true to trigger test failure also in s…
jtjeferreira Jan 15, 2025
25045de
supportSavepointRelease
jtjeferreira Jan 15, 2025
ce1db2f
fix: overrides
aldum Aug 30, 2025
a647a4d
style: use triple quotes instead of escaping
aldum Aug 30, 2025
d5f24da
fix: add missing tables in mssql seed
aldum Aug 30, 2025
f48b80e
fix: table name escape test
aldum Aug 30, 2025
2ea802e
test: add one more row for data types and options tests
aldum Aug 30, 2025
7ac63de
style: add some newlines for readability
aldum Aug 30, 2025
db23ca1
feat: special case Column in boolean rendering
aldum Aug 30, 2025
8e674bc
feat: add value marker to Context
aldum Aug 30, 2025
dec3655
feat: mark inserts as value expressions
aldum Aug 30, 2025
1aaa554
feat: branch mssql booleans on value marker
aldum Aug 30, 2025
16b1e21
test: add more bool-specific MSSQL dialect tests
aldum Aug 30, 2025
b75e72f
fix: mark updates as values
aldum Aug 30, 2025
5c50041
fix: ignore updateGetGeneratedKeysSql from recorded tests
aldum Sep 2, 2025
9530e3f
doc: regenerate docs
aldum Aug 30, 2025
5986ada
doc: comment on MSSQL dialect test availability
aldum Sep 9, 2025
4ceb0b0
doc: explain why moreValues were added for nulls first/last tests
aldum Sep 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions build.mill
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ trait CommonBase extends ScalaModule with PublishModule with ScalafixModule { co
ivy"org.postgresql:postgresql:42.6.0",
ivy"org.testcontainers:mysql:1.19.1",
ivy"mysql:mysql-connector-java:8.0.33",
ivy"org.testcontainers:mssqlserver:1.19.1",
ivy"com.microsoft.sqlserver:mssql-jdbc:12.8.1.jre11",
ivy"com.zaxxer:HikariCP:5.1.0"
)

Expand Down
157 changes: 144 additions & 13 deletions docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,10 @@ dbClient.transaction { db =>
LocalDate.parse("2000-01-01")
)
)
assert(generatedKeys == Seq(4, 5))
if (!this.isInstanceOf[MsSqlSuite])
assert(generatedKeys == Seq(4, 5))
else
assert(generatedKeys == Seq(5))

db.run(Buyer.select) ==> List(
Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")),
Expand Down Expand Up @@ -1997,7 +2000,6 @@ Buyer.select
.leftJoin(ShippingInfo)(_.id `=` _.buyerId)
.map { case (b, si) => (b.name, si.map(_.shippingDate)) }
.sortBy(_._2)
.nullsFirst
```


Expand All @@ -2006,7 +2008,7 @@ Buyer.select
SELECT buyer0.name AS res_0, shipping_info1.shipping_date AS res_1
FROM buyer buyer0
LEFT JOIN shipping_info shipping_info1 ON (buyer0.id = shipping_info1.buyer_id)
ORDER BY res_1 NULLS FIRST
ORDER BY res_1
```


Expand Down Expand Up @@ -3395,7 +3397,7 @@ Purchase.delete(_ => true)

*
```sql
DELETE FROM purchase WHERE ?
DELETE FROM purchase
```


Expand Down Expand Up @@ -4003,7 +4005,7 @@ Product.select


## UpdateJoin
`UPDATE` queries that use `JOIN`s
Basic `UPDATE` queries
### UpdateJoin.join

ScalaSql supports performing `UPDATE`s with `FROM`/`JOIN` clauses using the
Expand Down Expand Up @@ -6951,7 +6953,7 @@ Select.delete(_ => true)

*
```sql
DELETE FROM "select" WHERE ?
DELETE FROM "select"
```


Expand Down Expand Up @@ -9774,7 +9776,7 @@ Expr(Bytes("Hello")).contains(Bytes("ll"))


## ExprMathOps
Math operations; supported by H2/Postgres/MySql, not supported by Sqlite
Math operations; supported by H2/Postgres/MySql/MsSql, not supported by Sqlite
### ExprMathOps.power


Expand Down Expand Up @@ -10111,7 +10113,7 @@ val value = DataTypes[Sc](
myInt = 12345678,
myBigInt = 12345678901L,
myDouble = 3.14,
myBoolean = true,
myBoolean = false,
myLocalDate = LocalDate.parse("2023-12-20"),
myLocalTime = LocalTime.parse("10:15:30"),
myLocalDateTime = LocalDateTime.parse("2011-12-03T10:15:30"),
Expand All @@ -10122,6 +10124,23 @@ val value = DataTypes[Sc](
myEnum = MyEnum.bar
)

val value2 = DataTypes[Sc](
67.toByte,
mySmallInt = 32767.toShort,
myInt = 12345678,
myBigInt = 9876543210L,
myDouble = 2.71,
myBoolean = true,
myLocalDate = LocalDate.parse("2020-02-22"),
myLocalTime = LocalTime.parse("03:05:01"),
myLocalDateTime = LocalDateTime.parse("2021-06-07T02:01:03"),
myUtilDate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS").parse("2021-06-07T02:01:03.000"),
myInstant = Instant.parse("2021-06-07T02:01:03Z"),
myVarBinary = new geny.Bytes(Array[Byte](9, 8, 7, 6, 5, 4, 3, 2)),
myUUID = new java.util.UUID(9876543210L, 1234567890L),
myEnum = MyEnum.baz
)

db.run(
DataTypes.insert.columns(
_.myTinyInt := value.myTinyInt,
Expand All @@ -10140,8 +10159,26 @@ db.run(
_.myEnum := value.myEnum
)
) ==> 1
db.run(
DataTypes.insert.columns(
_.myTinyInt := value2.myTinyInt,
_.mySmallInt := value2.mySmallInt,
_.myInt := value2.myInt,
_.myBigInt := value2.myBigInt,
_.myDouble := value2.myDouble,
_.myBoolean := value2.myBoolean,
_.myLocalDate := value2.myLocalDate,
_.myLocalTime := value2.myLocalTime,
_.myLocalDateTime := value2.myLocalDateTime,
_.myUtilDate := value2.myUtilDate,
_.myInstant := value2.myInstant,
_.myVarBinary := value2.myVarBinary,
_.myUUID := value2.myUUID,
_.myEnum := value2.myEnum
)
) ==> 1

db.run(DataTypes.select) ==> Seq(value)
db.run(DataTypes.select) ==> Seq(value, value2)
```


Expand Down Expand Up @@ -11181,6 +11218,24 @@ val rowSome = OptDataTypes[Sc](
myUUID = Some(new java.util.UUID(1234567890L, 9876543210L)),
myEnum = Some(MyEnum.bar)
)
val rowSome2 = OptDataTypes[Sc](
myTinyInt = Some(67.toByte),
mySmallInt = Some(32767.toShort),
myInt = Some(23456789),
myBigInt = Some(9876543210L),
myDouble = Some(2.71),
myBoolean = Some(false),
myLocalDate = Some(LocalDate.parse("2020-02-22")),
myLocalTime = Some(LocalTime.parse("03:05:01")),
myLocalDateTime = Some(LocalDateTime.parse("2021-06-07T02:01:03")),
myUtilDate = Some(
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS").parse("2021-06-07T02:01:03.000")
),
myInstant = Some(Instant.parse("2021-06-07T02:01:03Z")),
myVarBinary = Some(new geny.Bytes(Array[Byte](9, 8, 7, 6, 5, 4, 3, 2))),
myUUID = Some(new java.util.UUID(9876543210L, 1234567890L)),
myEnum = Some(MyEnum.baz)
)

val rowNone = OptDataTypes[Sc](
myTinyInt = None,
Expand All @@ -11198,12 +11253,11 @@ val rowNone = OptDataTypes[Sc](
myUUID = None,
myEnum = None
)

db.run(
OptDataTypes.insert.values(rowSome, rowNone)
) ==> 2
OptDataTypes.insert.values(rowSome, rowSome2, rowNone)
) ==> 3

db.run(OptDataTypes.select) ==> Seq(rowSome, rowNone)
db.run(OptDataTypes.select) ==> Seq(rowSome, rowSome2, rowNone)
```


Expand Down Expand Up @@ -12376,3 +12430,80 @@ db.concatWs(" ", "i", "am", "cow", 1337)
```



## MsSqlDialect
Operations specific to working with Microsoft SQL Databases
### MsSqlDialect.top

For ScalaSql's Microsoft SQL dialect provides, the `.take(n)` operator translates
into a SQL `TOP(n)` clause

```scala
Buyer.select.take(0)
```


*
```sql
SELECT TOP(?) buyer0.id AS id, buyer0.name AS name, buyer0.date_of_birth AS date_of_birth
FROM buyer buyer0
```



*
```scala
Seq[Buyer[Sc]]()
```



### MsSqlDialect.bool vs bit

Insert rows with BIT values

```scala
db.run(
BoolTypes.insert.columns(
_.nullable := value.nullable,
_.nonNullable := value.nonNullable,
_.a := value.a,
_.b := value.b,
_.comment := value.comment
)
) ==> 1
db.run(
BoolTypes.insert.columns(
_.nullable := value2.nullable,
_.nonNullable := value2.nonNullable,
_.a := value2.a,
_.b := value2.b,
_.comment := value2.comment
)
) ==> 1
```






### MsSqlDialect.uodate BIT



```scala
BoolTypes
.update(_.a `=` 1)
.set(_.nonNullable := true)
```


*
```sql
UPDATE bool_types SET non_nullable = ? WHERE (bool_types.a = ?)
```




20 changes: 19 additions & 1 deletion scalasql/core/src/Context.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ trait Context {
*/
def exprNaming: Map[Expr.Identity, SqlStr]

/**
* Mark [[Expr]]s as a raw value for an INSERT or UPDATE context
*/
def valueMarker: Boolean

/**
* The ScalaSql configuration
*/
Expand All @@ -28,9 +33,11 @@ trait Context {

def withFromNaming(fromNaming: Map[Context.From, String]): Context
def withExprNaming(exprNaming: Map[Expr.Identity, SqlStr]): Context
def markAsValue: Context
}

object Context {

trait From {

/**
Expand Down Expand Up @@ -58,13 +65,18 @@ object Context {
case class Impl(
fromNaming: Map[From, String],
exprNaming: Map[Expr.Identity, SqlStr],
valueMarker: Boolean,
config: Config,
dialectConfig: DialectConfig
) extends Context {
def withFromNaming(fromNaming: Map[From, String]): Context = copy(fromNaming = fromNaming)

def withExprNaming(exprNaming: Map[Expr.Identity, SqlStr]): Context =
copy(exprNaming = exprNaming)

def markAsValue: Context = copy(
valueMarker = true
)
}

/**
Expand Down Expand Up @@ -96,7 +108,13 @@ object Context {
.map { case (e, s) => (e, sql"${SqlStr.raw(newFromNaming(t), Array(e))}.$s") }
}

Context.Impl(newFromNaming, newExprNaming, prevContext.config, prevContext.dialectConfig)
Context.Impl(
newFromNaming,
newExprNaming,
prevContext.valueMarker,
prevContext.config,
prevContext.dialectConfig
)
}

}
4 changes: 2 additions & 2 deletions scalasql/core/src/DbApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ object DbApi {
config: Config,
dialectConfig: DialectConfig
) = {
val ctx = Context.Impl(Map(), Map(), config, dialectConfig)
val ctx = Context.Impl(Map(), Map(), false, config, dialectConfig)
val flattened = SqlStr.flatten(qr.renderSql(query, ctx))
flattened
}
Expand Down Expand Up @@ -583,7 +583,7 @@ object DbApi {

try {
val res = block(new DbApi.SavepointImpl(savepoint, () => rollbackSavepoint(savepoint)))
if (savepointStack.lastOption.exists(_ eq savepoint)) {
if (dialect.supportSavepointRelease && savepointStack.lastOption.exists(_ eq savepoint)) {
// Only release if this savepoint has not been rolled back,
// directly or indirectly
connection.releaseSavepoint(savepoint)
Expand Down
4 changes: 3 additions & 1 deletion scalasql/core/src/DialectConfig.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package scalasql.core
trait DialectConfig { that =>
def castParams: Boolean
def escape(str: String): String
def supportSavepointRelease: Boolean

def withCastParams(params: Boolean) = new DialectConfig {
def castParams: Boolean = params

def escape(str: String): String = that.escape(str)
def supportSavepointRelease = that.supportSavepointRelease

def escape(str: String): String = that.escape(str)
}
}
11 changes: 9 additions & 2 deletions scalasql/query/src/CompoundSelect.scala
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ object CompoundSelect {
// columns are duplicates or not, and thus what final set of rows is returned
lazy val preserveAll = query.compoundOps.exists(_.op != "UNION ALL")

def render(liveExprs: LiveExprs) = {
protected def prerender(liveExprs: LiveExprs) = {
val innerLiveExprs =
if (preserveAll) LiveExprs.none
else liveExprs.map(_ ++ newReferencedExpressions)
Expand All @@ -138,7 +138,14 @@ object CompoundSelect {
SqlStr.join(compoundStrs)
}

lhsStr + compound + sortOpt + limitOpt + offsetOpt
(lhsStr, compound, sortOpt, limitOpt, offsetOpt)
}

def render(liveExprs: LiveExprs) = {
prerender(liveExprs) match {
case (lhsStr, compound, sortOpt, limitOpt, offsetOpt) =>
lhsStr + compound + sortOpt + limitOpt + offsetOpt
}
}
def orderToSqlStr(newCtx: Context) =
CompoundSelect.orderToSqlStr(query.orderBy, newCtx, gap = true)
Expand Down
8 changes: 4 additions & 4 deletions scalasql/query/src/Delete.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package scalasql.query

import scalasql.core.DialectTypeMappers
import scalasql.core.Context
import scalasql.core.{Queryable, SqlStr, Expr}
import scalasql.core.{Context, DialectTypeMappers, Expr, ExprsToSql, Queryable, SqlStr}
import scalasql.core.SqlStr.SqlStringSyntax

/**
Expand All @@ -26,6 +24,8 @@ object Delete {
lazy val tableNameStr =
SqlStr.raw(Table.fullIdentifier(table.value))

def render() = sql"DELETE FROM $tableNameStr WHERE $expr"
lazy val filtersOpt = SqlStr.flatten(ExprsToSql.booleanExprs(sql" WHERE ", expr :: Nil))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe this could be extracted to a separate PR?


def render() = sql"DELETE FROM $tableNameStr$filtersOpt"
}
}
Loading