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

Enhancement/2025 02 data type column #388

Merged
merged 20 commits into from
Feb 16, 2025
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
3 changes: 2 additions & 1 deletion benchmark/src/main/scala/benchmark/Model.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ package benchmark

import ldbc.dsl.codec.*

import ldbc.query.builder.formatter.Naming
import ldbc.statement.formatter.Naming

import ldbc.query.builder.Table

given Naming = Naming.PASCAL
Expand Down
6 changes: 6 additions & 0 deletions module/ldbc-dsl/src/main/scala/ldbc/dsl/codec/Encoder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ trait Encoder[A]:
/** `Encoder` is semigroupal: a pair of encoders make a encoder for a pair. */
def product[B](that: Encoder[B]): Encoder[(A, B)] = (value: (A, B)) => encode(value._1) product that.encode(value._2)

/** Lift this `Decoder` into `Option`. */
def opt: Encoder[Option[A]] = {
case Some(value) => this.encode(value)
case None => Encoder.Encoded.success(List.empty)
}

object Encoder extends TwiddleSyntax[Encoder]:

/** Types that can be handled by PreparedStatement. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import scala.quoted.*
import ldbc.dsl.codec.*

import ldbc.statement.{ AbstractTable, Column }
import ldbc.statement.formatter.Naming

import ldbc.query.builder.formatter.Naming
import ldbc.query.builder.interpreter.*
import ldbc.query.builder.Column as ColumnAnnotation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ object Character:
* A model for representing collations to be set in column definitions for the string data types CHAR, VARCHAR, TEXT,
* ENUM, SET, and any synonym.
*/
trait Collate[T <: Collate.COLLATION_TYPE] extends Attribute[T]:
trait Collate[T] extends Attribute[T]:

/** Collate name */
def name: String
Expand Down
290 changes: 290 additions & 0 deletions module/ldbc-schema/src/main/scala/ldbc/schema/DataTypeColumn.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
/**
* Copyright (c) 2023-2024 by Takahiko Tominaga
* This software is licensed under the MIT License (MIT).
* For more information see LICENSE or https://opensource.org/licenses/MIT
*/

package ldbc.schema

import ldbc.dsl.codec.*

import ldbc.statement.Column

import ldbc.schema.attribute.{ Attribute, AutoInc }

sealed trait DataTypeColumn[T] extends Column[T]:

/**
* Data type to be set for the column
*/
def dataType: DataType[T]

/**
* List of attributes to be set for the column
*/
def attributes: List[Attribute[T]]

/**
* Default value to be set for the column
*/
def defaultValue: Option[Default]

/**
* Method for setting the default value to the current time.
*/
def default(value: T): DataTypeColumn[T]

/**
* Method for setting the default value to NULL.
*/
def defaultNull: DataTypeColumn[T]

/**
* Method for setting the attributes to the column.
*/
def setAttributes(attributes: Attribute[T]*): DataTypeColumn[T]

/**
* Value indicating whether DataType is null-allowed or not.
*
* @return
* true if NULL is allowed, false if NULL is not allowed
*/
def isOptional: Boolean

/**
* Value to indicate whether NULL is acceptable as a query string in SQL
*/
protected def nullType: String = if isOptional then "NULL" else "NOT NULL"

object DataTypeColumn:

sealed trait NumericColumn[T] extends DataTypeColumn[T]:

/**
* Method for setting data type to unsigned.
*/
def unsigned: NumericColumn[T]

/**
* Method for setting the default value to AUTO_INCREMENT.
*/
def autoIncrement: NumericColumn[T]

sealed trait StringColumn[T] extends DataTypeColumn[T]:

/**
* Method for setting Character Set to DataType in SQL.
*
* @param character
* Character Set
*/
def charset(character: Character): StringColumn[T]

/**
* Method for setting Collation to DataType in SQL.
*
* @param collate
* Collation
*/
def collate(collate: Collate[T]): StringColumn[T]

sealed trait TemporalColumn[T] extends DataTypeColumn[T]:

/**
* Method for setting the default value to the current time.
*/
def defaultCurrentTimestamp(onUpdate: Boolean = false): TemporalColumn[T]

/**
* Method for setting the default value to the current date.
*/
def defaultCurrentDate: TemporalColumn[T]

private[ldbc] case class NumericColumnImpl[T](
name: String,
alias: Option[String],
decoder: Decoder[T],
encoder: Encoder[T],
dataType: DataType[T],
isUnsigned: Boolean = false,
isOptional: Boolean = true,
defaultValue: Option[Default] = None,
attributes: List[Attribute[T]] = List.empty[Attribute[T]]
) extends NumericColumn[T]:

override def as(name: String): Column[T] =
this.copy(alias = Some(name))

override def statement: String =
(List(
Some(name),
Some(dataType.typeName),
if isUnsigned then Some("UNSIGNED") else None,
Some(nullType),
defaultValue.map(_.queryString)
).flatten ++ attributes.map(_.queryString)).mkString(" ")
override def updateStatement: String = s"$name = ?"
override def duplicateKeyUpdateStatement: String = s"$name = VALUES(${ alias.getOrElse(name) })"

override def default(value: T): DataTypeColumn[T] = value match
case v: Option[?] => this.copy(defaultValue = Some(v.fold(Default.Null)(Default.Value(_))))
case v => this.copy(defaultValue = Some(Default.Value(v)))
override def defaultNull: DataTypeColumn[T] = this.copy(defaultValue = Some(Default.Null))

override def setAttributes(attributes: Attribute[T]*): DataTypeColumn[T] = this.copy(attributes = attributes.toList)
override def autoIncrement: NumericColumn[T] = this.copy(attributes = AutoInc[T]() :: attributes)

override def unsigned: NumericColumn[T] = this.copy(isUnsigned = true)

private[ldbc] case class StringColumnImpl[T](
name: String,
alias: Option[String],
decoder: Decoder[T],
encoder: Encoder[T],
dataType: DataType[T],
isOptional: Boolean = true,
attributes: List[Attribute[T]] = List.empty[Attribute[T]],
defaultValue: Option[Default] = None,
character: Option[Character] = None,
collate: Option[Collate[T]] = None
) extends StringColumn[T]:

override def as(name: String): Column[T] =
this.copy(alias = Some(name))

override def statement: String =
(List(
Some(name),
Some(dataType.typeName),
character.map(_.queryString),
collate.map(_.queryString),
Some(nullType),
defaultValue.map(_.queryString)
).flatten ++ attributes.map(_.queryString)).mkString(" ")
override def updateStatement: String = s"$name = ?"
override def duplicateKeyUpdateStatement: String = s"$name = VALUES(${ alias.getOrElse(name) })"

override def default(value: T): DataTypeColumn[T] = value match
case v: Option[?] => this.copy(defaultValue = Some(v.fold(Default.Null)(Default.Value(_))))
case v => this.copy(defaultValue = Some(Default.Value(v)))
override def defaultNull: DataTypeColumn[T] = this.copy(defaultValue = Some(Default.Null))

override def setAttributes(attributes: Attribute[T]*): DataTypeColumn[T] = this.copy(attributes = attributes.toList)

override def charset(character: Character): StringColumn[T] = this.copy(character = Some(character))
override def collate(collate: Collate[T]): StringColumn[T] = this.copy(collate = Some(collate))

private[ldbc] case class TemporalColumnImpl[T](
name: String,
alias: Option[String],
decoder: Decoder[T],
encoder: Encoder[T],
dataType: DataType[T],
isOptional: Boolean = true,
attributes: List[Attribute[T]] = List.empty[Attribute[T]],
defaultValue: Option[Default] = None
) extends TemporalColumn[T]:

override def as(name: String): Column[T] =
this.copy(alias = Some(name))

override def statement: String =
(List(
Some(name),
Some(dataType.typeName),
Some(nullType),
defaultValue.map(_.queryString)
).flatten ++ attributes.map(_.queryString)).mkString(" ")
override def updateStatement: String = s"$name = ?"
override def duplicateKeyUpdateStatement: String = s"$name = VALUES(${ alias.getOrElse(name) })"

override def default(value: T): DataTypeColumn[T] = value match
case v: Option[?] => this.copy(defaultValue = Some(v.fold(Default.Null)(Default.Value(_))))
case v => this.copy(defaultValue = Some(Default.Value(v)))
override def defaultNull: DataTypeColumn[T] = this.copy(defaultValue = Some(Default.Null))

override def setAttributes(attributes: Attribute[T]*): DataTypeColumn[T] = this.copy(attributes = attributes.toList)

override def defaultCurrentTimestamp(onUpdate: Boolean = false): TemporalColumn[T] =
this.copy(defaultValue = Some(Default.TimeStamp(None, onUpdate)))
override def defaultCurrentDate: TemporalColumn[T] = this.copy(defaultValue = Some(Default.Date()))

private[ldbc] case class Impl[T](
name: String,
alias: Option[String],
decoder: Decoder[T],
encoder: Encoder[T],
dataType: DataType[T],
isOptional: Boolean = true,
attributes: List[Attribute[T]] = List.empty[Attribute[T]],
defaultValue: Option[Default] = None
) extends DataTypeColumn[T]:

override def as(name: String): Column[T] =
this.copy(alias = Some(name))

override def statement: String =
(List(
Some(name),
Some(dataType.typeName),
Some(nullType),
defaultValue.map(_.queryString)
).flatten ++ attributes.map(_.queryString)).mkString(" ")
override def updateStatement: String = s"$name = ?"
override def duplicateKeyUpdateStatement: String = s"$name = VALUES(${ alias.getOrElse(name) })"

override def default(value: T): DataTypeColumn[T] = value match
case v: Option[?] => this.copy(defaultValue = Some(v.fold(Default.Null)(Default.Value(_))))
case v => this.copy(defaultValue = Some(Default.Value(v)))
override def defaultNull: DataTypeColumn[T] = this.copy(defaultValue = Some(Default.Null))

override def setAttributes(attributes: Attribute[T]*): DataTypeColumn[T] = this.copy(attributes = attributes.toList)

def apply[T](name: String, alias: Option[String], dataType: DataType[T], isOptional: Boolean)(using
codec: Codec[T]
): DataTypeColumn[T] =
Impl[T](
s"`$name`",
alias.map(v => s"$v.`$name`"),
codec.asDecoder,
codec.asEncoder,
dataType,
isOptional = isOptional
)

def numeric[T](name: String, alias: Option[String], dataType: DataType[T], isOptional: Boolean)(using
codec: Codec[T]
): NumericColumn[T] =
NumericColumnImpl[T](
s"`$name`",
alias.map(v => s"$v.`$name`"),
codec.asDecoder,
codec.asEncoder,
dataType,
isOptional = isOptional
)

def string[T](name: String, alias: Option[String], dataType: DataType[T], isOptional: Boolean)(using
codec: Codec[T]
): StringColumn[T] =
StringColumnImpl[T](
s"`$name`",
alias.map(v => s"$v.`$name`"),
codec.asDecoder,
codec.asEncoder,
dataType,
isOptional = isOptional
)

def temporal[T](name: String, alias: Option[String], dataType: DataType[T], isOptional: Boolean)(using
codec: Codec[T]
): TemporalColumn[T] =
TemporalColumnImpl[T](
s"`$name`",
alias.map(v => s"$v.`$name`"),
codec.asDecoder,
codec.asEncoder,
dataType,
isOptional = isOptional
)
Loading