Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Labelled placeholders #140

Merged
merged 4 commits into from
Nov 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 1 addition & 1 deletion .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version = "3.8.0"
version = "3.8.3"
runner.dialect = scala3
rewrite.scala3.insertEndMarkerMinLines = 10
rewrite.scala3.removeOptionalBraces = true
Expand Down
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,15 @@ def example(using Database, Zone) =
sql"select count(*) from my_table".count()
sql"select count(*) from my_table where x = $int4 and y = $text".count(25 -> "hello")

// Label codecs for clarity
val id = text.label["id"]
val name = text.label["name"]

sql"select count(*) from my_table where id = $id or name = $name or age = $int4".one(
("id" -> "id_1", "name" -> "tony", 50),
int4
)

case class Data(key: Int, value: String)
val rc = (int4 ~ text).as[Data]
val tableName = "my_table"
Expand Down Expand Up @@ -118,7 +127,7 @@ def example_failure(using Database, Zone) =
sql"select count(*) from my_table where x = $int4 and y = $text".count("hello" -> 25)
```

`sql` interpolator produces a `Query`, so you can use it as described in the previous section
`sql` interpolator produces a `Query`, so you can use it as described in the previous section.

### Fragments

Expand Down
3 changes: 2 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import scala.scalanative.build.SourceLevelDebuggingConfig
import bindgen.plugin.BindgenMode
import java.nio.file.Paths

Expand Down Expand Up @@ -32,7 +33,7 @@ lazy val core =
)
.settings(common)
.settings(
Test / nativeConfig ~= (_.withEmbedResources(true)),
Test / nativeConfig ~= (_.withEmbedResources(true).withSourceLevelDebuggingConfig(SourceLevelDebuggingConfig.enabled)),
Compile / bindgenBindings += {
val configurator = vcpkgConfigurator.value

Expand Down
27 changes: 27 additions & 0 deletions module-core/src/main/scala/roach/LabelledCodec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package roach

import scala.scalanative.unsafe.CString

import scala.ContextFunction1

import scala.scalanative.unsafe.Zone

class LabelledCodec[L <: Singleton & String, T](lab: L, c: Codec[T]) extends Codec[(L, T)]:
override def length: Int = c.length

override def decode(get: Int => CString, isNull: Int => Boolean)(using
Zone
): (L, T) =
lab -> c.decode(get, isNull)

override def encode(value: (L, T)): Int => (Zone) ?=> CString =
c.encode(value._2)

export c.accepts

override def toString(): String = s"LabelledCodec[$lab, $c]"
end LabelledCodec

extension [T](c: Codec[T])
inline def label[L <: Singleton & String]: LabelledCodec[L, T] =
LabelledCodec(compiletime.constValue[L], c)
134 changes: 84 additions & 50 deletions module-core/src/main/scala/roach/Query.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,56 +3,90 @@ package roach
import scala.scalanative.unsafe.Zone
import scala.util.NotGiven

opaque type Query[T] = T => (Database, Zone) ?=> roach.Result
// class Query:
// def all[T](using NotGiven[T])

trait Query[T]:
self =>
protected def execute(data: T)(using db: Database, z: Zone): roach.Result

def contramap[X](transform: X => T): Query[X] =
new Query[X]:
override protected def execute(
data: X
)(using db: Database, z: Zone): Result = self.execute(transform(data))

override def query: String = self.query

def all[X](data: T, codec: Codec[X])(using
iz: Zone,
db: Database
): Vector[X] =
execute(data).use(_.readAll(codec))

def execute[X](data: T, codec: Codec[X])(using
iz: Zone,
db: Database
): Option[X] =
execute(data).use(_.readOne(codec))

def exec(data: T)(using
iz: Zone,
db: Database
): Unit =
execute(data).use { _ => }

def count(data: T)(using
iz: Zone,
db: Database
): Int =
execute(data).use(_.count())

def one[X](data: T, codec: Codec[X])(using
iz: Zone,
db: Database
): Option[X] =
execute(data).use(_.readOne(codec))

def query: String
end Query

private class QueryImpl[T](val query: String, codec: Codec[T]) extends Query[T]:
protected def execute(data: T)(using db: Database, z: Zone): roach.Result =
db.executeParams[T](query, codec, data).getOrThrow

class VoidQuery(str: String):
private def execute()(using db: Database, z: Zone): roach.Result =
db.execute(str).getOrThrow

def all[X](codec: Codec[X])(using iz: Zone, db: Database): Vector[X] =
execute().use(_.readAll(codec)(using iz))

def one[X](codec: Codec[X])(using iz: Zone, db: Database): Option[X] =
execute().use(_.readOne(codec)(using iz))

def exec()(using iz: Zone, db: Database): Unit =
execute().use { _ => }

def count()(using
iz: Zone,
db: Database
): Int =
execute().use(_.count())

end VoidQuery

object Query:
def apply[T](q: String, codecIn: Codec[T]): Query[T] =
data => (db, z) ?=> db.executeParams[T](q, codecIn, data).getOrThrow

def apply(q: String): Query[Unit] =
data => (db, z) ?=> db.execute(q).getOrThrow

extension [T](q: Query[T])(using
NotGiven[T =:= Unit]
)
def all[X](data: T, codec: Codec[X])(using
iz: Zone,
db: Database
): Vector[X] =
q(data).use(_.readAll(codec)(using iz))

def one[X](data: T, codec: Codec[X])(using
iz: Zone,
db: Database
): Option[X] =
q(data).use(_.readOne(codec)(using iz))

def exec(data: T)(using
iz: Zone,
db: Database
): Unit =
q(data).use { _ => }

def count(data: T)(using
iz: Zone,
db: Database
): Int =
q(data).use(_.count())
end extension

extension (q: Query[Unit])
def all[X](codec: Codec[X])(using iz: Zone, db: Database): Vector[X] =
q(()).use(_.readAll(codec)(using iz))

def one[X](codec: Codec[X])(using iz: Zone, db: Database): Option[X] =
q(()).use(_.readOne(codec)(using iz))

def exec()(using iz: Zone, db: Database): Unit =
q(()).use { _ => }

def count()(using
iz: Zone,
db: Database
): Int =
q(()).use(_.count())
end extension
new QueryImpl(q, codecIn)

def apply(q: String): VoidQuery =
new VoidQuery(q)

private[roach] def applyTransformed[Positional, UserSupplied](
q: String,
codecIn: Codec[Positional],
transform: UserSupplied => Positional
): Query[UserSupplied] =
apply(q, codecIn).contramap(transform)
end Query
78 changes: 69 additions & 9 deletions module-core/src/main/scala/roach/codec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,56 @@ trait ValueCodec[T] extends Codec[T]:

end ValueCodec

private[roach] class AutoTupledCodec[A](a: Codec[A])
extends Codec[A *: EmptyTuple]:

def accepts(offset: Int) = a.accepts(offset)

def length = a.length

def decode(get: Int => CString, isNull: Int => Boolean)(using
Zone
): Tuple1[A] =
val left = a.decode(get, isNull)

Tuple1(left)

override def encode(value: A *: EmptyTuple) =
a.encode(value._1)

override def toString() =
s"AutoTupledCodec[$a]"
end AutoTupledCodec

private[roach] class TupleCodec[A, B](a: Codec[A], b: Codec[B])
extends Codec[(A, B)]:
def accepts(offset: Int) =
if offset < a.length then a.accepts(offset)
else b.accepts(offset - a.length)

def length = a.length + b.length

def decode(get: Int => CString, isNull: Int => Boolean)(using Zone): (A, B) =
val left = a.decode(get, isNull)
val right = b.decode(
(i: Int) => get(i + a.length),
(i: Int) => isNull(i + a.length)
)
(left, right)

def encode(value: (A, B)) =
val (left, right) = value
val leftEncode = a.encode(left)
val rightEncode = b.encode(right)

(offset: Int) =>
if offset + 1 > a.length then rightEncode(offset - a.length)
else leftEncode(offset)

override def toString() =
s"TupleCodec[$a, $b]"
end TupleCodec

private[roach] class AppendCodec[A <: Tuple, B](a: Codec[A], b: Codec[B])
extends Codec[Tuple.Concat[A, (B *: EmptyTuple)]]:
type T = Tuple.Concat[A, (B *: EmptyTuple)]
Expand All @@ -57,7 +107,8 @@ private[roach] class AppendCodec[A <: Tuple, B](a: Codec[A], b: Codec[B])
left ++ (right *: EmptyTuple)

def encode(value: T) =
val (left, right) = value.splitAt(a.length).asInstanceOf[(A, Tuple1[B])]
val (left, right) =
value.splitAt(value.size - 1).asInstanceOf[(A, B *: EmptyTuple)]
val leftEncode = a.encode(left)
val rightEncode = b.encode(right._1)

Expand All @@ -70,9 +121,11 @@ private[roach] class AppendCodec[A <: Tuple, B](a: Codec[A], b: Codec[B])

end AppendCodec

private[roach] class CombineCodec[A, B](a: Codec[A], b: Codec[B])
extends Codec[(A, B)]:
type T = (A, B)
private[roach] class CombineCodec[A <: Tuple, B <: Tuple](
a: Codec[A],
b: Codec[B]
) extends Codec[Tuple.Concat[A, B]]:
type T = Tuple.Concat[A, B]
def accepts(offset: Int) =
if offset < a.length then a.accepts(offset)
else b.accepts(offset - a.length)
Expand All @@ -83,11 +136,11 @@ private[roach] class CombineCodec[A, B](a: Codec[A], b: Codec[B])
val left = a.decode(get, isNull)
val right =
b.decode((i: Int) => get(i + a.length), (i: Int) => isNull(i + a.length))
(left, right)
left ++ right

def encode(value: T) =
val leftEncode = a.encode(value._1)
val rightEncode = b.encode(value._2)
val leftEncode = a.encode(value.take(a.length).asInstanceOf[A])
val rightEncode = b.encode(value.drop(a.length).asInstanceOf[B])

(offset: Int) =>
if offset + 1 > a.length then rightEncode(offset - a.length)
Expand Down Expand Up @@ -117,11 +170,15 @@ object Codec:
def encode(value: T) =
d.encode(iso.invert(value))

override def toString(): String = s"IsoCodec[$d, $iso]"

extension [A](d: Codec[A])
inline def ~[B](
other: Codec[B]
)(using NotGiven[B <:< Tuple]): Codec[(A, B)] =
CombineCodec(d, other)
TupleCodec(d, other)
// AppendCodec(AutoTupledCodec(d), other)

end extension

def stringLike[A](
Expand All @@ -137,7 +194,7 @@ object Codec:
def encode(value: A) =
_ => toCString(g(value))

override def toString() = s"ValueCodec[$accept]"
override def toString() = s"$accept"

end Codec

Expand All @@ -153,3 +210,6 @@ object Iso:
mir.fromProduct(a)
def invert(a: A) =
Tuple.fromProduct(a.asInstanceOf[Product]).asInstanceOf[X]

override def toString(): String = "Iso[" + mir.toString + "]"
end Iso
Loading
Loading