diff --git a/core/src/main/scala/ldbc/core/Table.scala b/core/src/main/scala/ldbc/core/Table.scala index 23fa2c1de..0ee5a72d0 100644 --- a/core/src/main/scala/ldbc/core/Table.scala +++ b/core/src/main/scala/ldbc/core/Table.scala @@ -22,12 +22,12 @@ private[ldbc] trait Table[P <: Product] extends Dynamic: /** Table Key definitions */ private[ldbc] def keyDefinitions: Seq[Key] - /** Table comment */ - private[ldbc] def comment: Option[String] - /** Table alias name */ private[ldbc] def alias: Option[String] + /** Additional table information */ + private[ldbc] def options: Seq[TableOption] + /** Methods for statically accessing column information held by a Table. * * @param tag @@ -50,12 +50,46 @@ private[ldbc] trait Table[P <: Product] extends Dynamic: */ private[ldbc] def all: List[Column[[A] => A => A]] + /** Method to retrieve all column information that a table has as a Tuple. + * + * @param mirror + * product isomorphism map + */ def *(using mirror: Mirror.ProductOf[P]): Tuple.Map[mirror.MirroredElemTypes, Column] + /** Methods for setting key information for tables. + * + * @param func + * Function to construct an expression using the columns that Table has. + */ def keySet(func: Table[P] => Key): Table[P] - def comment(str: String): Table[P] + /** Methods for setting multiple key information for a table. + * + * @param func + * Function to construct an expression using the columns that Table has. + */ + def keySets(func: Table[P] => Seq[Key]): Table[P] + /** Methods for setting additional information for the table. + * + * @param option + * Additional information to be given to the table. + */ + def setOption(option: TableOption): Table[P] + + /** Methods for setting multiple additional information for a table. + * + * @param options + * Additional information to be given to the table. + */ + def setOptions(options: Seq[TableOption]): Table[P] + + /** Methods for setting alias names for tables. + * + * @param name + * Alias name to be set for the table + */ def as(name: String): Table[P] object Table extends Dynamic: @@ -64,7 +98,7 @@ object Table extends Dynamic: _name: String, columns: Tuple.Map[T, Column], keyDefinitions: Seq[Key], - comment: Option[String], + options: Seq[TableOption], alias: Option[String] = None ) extends Table[P]: @@ -89,7 +123,12 @@ object Table extends Dynamic: override def keySet(func: Table[P] => Key): Table[P] = this.copy(keyDefinitions = this.keyDefinitions :+ func(this)) - override def comment(str: String): Table[P] = this.copy(comment = Some(str)) + override def keySets(func: Table[P] => Seq[Key]): Table[P] = + this.copy(keyDefinitions = this.keyDefinitions ++ func(this)) + + override def setOption(option: TableOption): Table[P] = this.copy(options = options :+ option) + + override def setOptions(options: Seq[TableOption]): Table[P] = this.copy(options = options) override def as(name: String): Table[P] = this.copy(alias = Some(name)) @@ -130,4 +169,4 @@ object Table extends Dynamic: )( _name: String, columns: Tuple.Map[mirror.MirroredElemTypes, Column] - ): Table[P] = Impl[P, mirror.MirroredElemTypes](_name, columns, Seq.empty, None) + ): Table[P] = Impl[P, mirror.MirroredElemTypes](_name, columns, Seq.empty, Seq.empty, None) diff --git a/core/src/main/scala/ldbc/core/TableOption.scala b/core/src/main/scala/ldbc/core/TableOption.scala new file mode 100644 index 000000000..3494750d0 --- /dev/null +++ b/core/src/main/scala/ldbc/core/TableOption.scala @@ -0,0 +1,91 @@ +/** This file is part of the ldbc. For the full copyright and license information, please view the LICENSE file that was + * distributed with this source code. + */ + +package ldbc.core + +trait TableOption: + + def queryString: String + +object TableOption: + case class AutoExtendSize(value: Int) extends TableOption: + override def queryString: String = s"AUTOEXTEND_SIZE=$value" + + case class AutoIncrement(value: Int) extends TableOption: + override def queryString: String = s"AUTO_INCREMENT=$value" + + case class AVGRowLength(value: Int) extends TableOption: + override def queryString: String = s"AVG_ROW_LENGTH=$value" + + case class Character(value: String) extends TableOption: + override def queryString: String = s"DEFAULT CHARACTER SET=$value" + + case class CheckSum(value: 0 | 1) extends TableOption: + override def queryString: String = s"CHECKSUM=$value" + + case class Collate(value: String) extends TableOption: + override def queryString: String = s"DEFAULT COLLATE=$value" + + case class Comment(value: String) extends TableOption: + override def queryString: String = s"COMMENT='$value'" + + case class Compression(value: "ZLIB" | "LZ4" | "NONE") extends TableOption: + override def queryString: String = s"COMPRESSION='$value'" + + case class Connection(value: String) extends TableOption: + override def queryString: String = s"CONNECTION='$value'" + + case class Directory(`type`: "DATA" | "INDEX", value: String) extends TableOption: + override def queryString: String = s"${ `type` } DIRECTORY='$value'" + + case class DelayKeyWrite(value: 0 | 1) extends TableOption: + override def queryString: String = s"DELAY_KEY_WRITE=$value" + + case class Encryption(value: "Y" | "N") extends TableOption: + override def queryString: String = s"ENCRYPTION='$value'" + + case class Engine( + value: "InnoDB" | "MyISAM" | "MEMORY" | "CSV" | "ARCHIVE" | "EXAMPLE" | "FEDERATED" | "HEAP" | "MERGE" | "NDB" + ) extends TableOption: + override def queryString: String = s"ENGINE=$value" + + case class EngineAttribute(value: String) extends TableOption: + override def queryString: String = s"ENGINE_ATTRIBUTE='$value'" + + case class InsertMethod(value: "NO" | "FIRST" | "LAST") extends TableOption: + override def queryString: String = s"INSERT_METHOD=$value" + + case class KeyBlockSize(value: 1 | 2 | 4 | 8 | 16) extends TableOption: + override def queryString: String = s"KEY_BLOCK_SIZE=$value" + + case class MaxRows(value: Long) extends TableOption: + override def queryString: String = s"MAX_ROWS=$value" + + case class MinRows(value: Long) extends TableOption: + override def queryString: String = s"MIN_ROWS=$value" + + case class PackKeys(value: "0" | "1" | "DEFAULT") extends TableOption: + override def queryString: String = s"PACK_KEYS=$value" + + case class RowFormat(value: "DEFAULT" | "DYNAMIC" | "FIXED" | "COMPRESSED" | "REDUNDANT" | "COMPACT") + extends TableOption: + override def queryString: String = s"ROW_FORMAT=$value" + + case class SecondaryEngineAttribute(value: String) extends TableOption: + override def queryString: String = s"SECONDARY_ENGINE_ATTRIBUTE='$value'" + + case class StatsAutoRecalc(value: "0" | "1" | "DEFAULT") extends TableOption: + override def queryString: String = s"STATS_AUTO_RECALC=$value" + + case class StatsPersistent(value: "0" | "1" | "DEFAULT") extends TableOption: + override def queryString: String = s"STATS_PERSISTENT=$value" + + case class StatsSamplePages(value: Int) extends TableOption: + override def queryString: String = s"STATS_SAMPLE_PAGES=$value" + + case class Tablespace(name: String, storage: Option["DISK" | "MEMORY"]) extends TableOption: + override def queryString: String = storage.fold(s"TABLESPACE $name")(v => s"TABLESPACE $name STORAGE $v") + + case class Union(tableNames: List[String]) extends TableOption: + override def queryString: String = s"UNION ${ tableNames.mkString(",") }" diff --git a/core/src/main/scala/ldbc/core/builder/TableQueryBuilder.scala b/core/src/main/scala/ldbc/core/builder/TableQueryBuilder.scala index 069f20ec9..c6fd97f7b 100644 --- a/core/src/main/scala/ldbc/core/builder/TableQueryBuilder.scala +++ b/core/src/main/scala/ldbc/core/builder/TableQueryBuilder.scala @@ -17,16 +17,18 @@ private[ldbc] case class TableQueryBuilder(table: Table[?]) extends TableValidat private val columnDefinitions: Seq[String] = table.all.map(_.queryString) - private val options: Seq[String] = + private val createDefinitions: Seq[String] = columnDefinitions ++ table.keyDefinitions.map(_.queryString) + private val tableOptions: Seq[String] = table.options.map(_.queryString) + /** Variable that generates the Create statement that creates the Table. */ lazy val createStatement: String = s""" |CREATE TABLE `${ table._name }` ( - | ${ options.mkString(",\n ") } - |); + | ${ createDefinitions.mkString(",\n ") } + |)${ if tableOptions.isEmpty then ";" else s" ${ tableOptions.mkString(" ") };" } |""".stripMargin /** Variable that generates the Drop statement that creates the Table. diff --git a/core/src/main/scala/ldbc/core/validator/TableValidator.scala b/core/src/main/scala/ldbc/core/validator/TableValidator.scala index 878c278a8..2ed43f9bb 100644 --- a/core/src/main/scala/ldbc/core/validator/TableValidator.scala +++ b/core/src/main/scala/ldbc/core/validator/TableValidator.scala @@ -16,11 +16,14 @@ private[ldbc] trait TableValidator: protected val autoInc = table.all.filter(_.attributes.contains(AutoInc())) - protected val primaryKey = table.all.filter(_.attributes.exists(_.isInstanceOf[PrimaryKey])) + protected val primaryKey = table.all.filter(_.attributes.exists { + case _: PrimaryKey => true + case _ => false + }) protected val keyPart = table.keyDefinitions.flatMap { - case key: PrimaryKey with Index => key.keyPart.toList - case key: UniqueKey with Index => key.keyPart.toList + case key: PrimaryKey with Index => key.keyPart + case key: UniqueKey with Index => key.keyPart case _ => List.empty } @@ -48,13 +51,17 @@ private[ldbc] trait TableValidator: require( !(autoInc.nonEmpty && - (autoInc.count(column => - column.attributes.exists(_.isInstanceOf[PrimaryKey]) || column.attributes.exists(_.isInstanceOf[UniqueKey]) - ) >= 1 || keyPart.count(key => + autoInc.count(column => + column.attributes.exists { + case _: PrimaryKey => true + case _: UniqueKey => true + case _ => false + } + ) == 0 && keyPart.count(key => autoInc .map(_.label) .contains(key.label) - ) == 0)), + ) == 0), "The columns with AUTO_INCREMENT must have a Primary Key or Unique Key." ) diff --git a/core/src/test/scala/ldbc/core/builder/TableQueryBuilderTest.scala b/core/src/test/scala/ldbc/core/builder/TableQueryBuilderTest.scala index b29abe24d..e58c44236 100644 --- a/core/src/test/scala/ldbc/core/builder/TableQueryBuilderTest.scala +++ b/core/src/test/scala/ldbc/core/builder/TableQueryBuilderTest.scala @@ -297,6 +297,31 @@ object TableQueryBuilderTest extends Specification: |""".stripMargin } + "If the table option is set to Table, the query string will match the specified value." in { + case class Test(id: Long, name: String) + + val table: Table[Test] = Table[Test]("test")( + column("id", BIGINT, AUTO_INCREMENT, PRIMARY_KEY), + column("name", VARCHAR(255)) + ) + .setOptions( + Seq( + TableOption.Engine("InnoDB"), + TableOption.Character("utf8mb4"), + TableOption.Collate("utf8mb4_unicode_ci"), + TableOption.Comment("test") + ) + ) + + TableQueryBuilder(table).createStatement === + """ + |CREATE TABLE `test` ( + | `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + | `name` VARCHAR(255) NOT NULL + |) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4 DEFAULT COLLATE=utf8mb4_unicode_ci COMMENT='test'; + |""".stripMargin + } + "IllegalArgumentException is raised if the type of the column set in FOREIGN KEY does not match." in { case class Test(id: Long, subId: Long) case class SubTest(id: Long, test: String) diff --git a/module/ldbc-codegen/src/main/scala/ldbc/codegen/TableModelGenerator.scala b/module/ldbc-codegen/src/main/scala/ldbc/codegen/TableModelGenerator.scala index b5859766b..2cc80d57d 100644 --- a/module/ldbc-codegen/src/main/scala/ldbc/codegen/TableModelGenerator.scala +++ b/module/ldbc-codegen/src/main/scala/ldbc/codegen/TableModelGenerator.scala @@ -85,6 +85,10 @@ private[ldbc] object TableModelGenerator: s".keySet(table => ${ key.toCode("table", classNameFormatter, propertyNameFormatter) })" ) + val tableOptions = statement.options.fold("")( + _.map(option => s".setOption(${ Table.buildTableOptionCode(option) })").mkString("\n ") + ) + val builder = ColumnCodeBuilder(classNameFormatter) val columns = @@ -111,6 +115,7 @@ private[ldbc] object TableModelGenerator: | ${ columns.mkString(",\n ") } | ) | ${ keyDefinitions.mkString("\n ") } + | $tableOptions |""".stripMargin Files.write(outputFile.toPath, scalaSource.getBytes(summon[Codec].name)) diff --git a/module/ldbc-codegen/src/main/scala/ldbc/codegen/model/Table.scala b/module/ldbc-codegen/src/main/scala/ldbc/codegen/model/Table.scala index f0259133b..65059f1ca 100644 --- a/module/ldbc-codegen/src/main/scala/ldbc/codegen/model/Table.scala +++ b/module/ldbc-codegen/src/main/scala/ldbc/codegen/model/Table.scala @@ -4,47 +4,44 @@ package ldbc.codegen.model -object Table: - - trait Options - - object Options: +import ldbc.core.TableOption - case class AutoExtendSize(value: Int) extends Options - case class AutoIncrement(value: Int) extends Options - case class AVGRowLength(value: Int) extends Options - case class Character(value: String) extends Options - case class CheckSum(value: 0 | 1) extends Options - case class Collate(value: String) extends Options - case class Comment(value: String) extends Options - case class Compression(value: "ZLIB" | "LZ4" | "NONE") extends Options - case class Connection(value: String) extends Options - case class Directory(value: String) extends Options - case class DelayKeyWrite(value: 0 | 1) extends Options - case class Encryption(value: "Y" | "N") extends Options - case class Engine( - value: "InnoDB" | "MyISAM" | "MEMORY" | "CSV" | "ARCHIVE" | "EXAMPLE" | "FEDERATED" | "HEAP" | "MERGE" | "NDB" - ) extends Options - case class EngineAttribute(value: Key.EngineAttribute) extends Options - case class InsertMethod(value: "NO" | "FIRST" | "LAST") extends Options - case class KeyBlockSize(value: Key.KeyBlockSize) extends Options - case class MaxRows(value: Long) extends Options - case class MinRows(value: Long) extends Options - case class PackKeys(value: "0" | "1" | "DEFAULT") extends Options - case class RowFormat(value: "DEFAULT" | "DYNAMIC" | "FIXED" | "COMPRESSED" | "REDUNDANT" | "COMPACT") - extends Options - case class SecondaryEngineAttribute(value: String) extends Options - case class StatsAutoRecalc(value: "0" | "1" | "DEFAULT") extends Options - case class StatsPersistent(value: "0" | "1" | "DEFAULT") extends Options - case class StatsSamplePages(value: Int) extends Options - case class Tablespace(name: String, storage: Option["DISK" | "MEMORY"]) extends Options - case class Union(tableNames: List[String]) extends Options +object Table: case class CreateStatement( tableName: String, columnDefinitions: List[ColumnDefinition], keyDefinitions: List[Key], - options: Option[List[Options]] + options: Option[List[TableOption]] ) case class DropStatement(tableName: String) + + def buildTableOptionCode(option: TableOption): String = + option match + case TableOption.AutoExtendSize(value) => s"TableOption.AutoExtendSize($value)" + case TableOption.AutoIncrement(value) => s"TableOption.AutoIncrement($value)" + case TableOption.AVGRowLength(value) => s"TableOption.AVGRowLength($value)" + case TableOption.Character(value) => s"TableOption.Character(\"$value\")" + case TableOption.CheckSum(value) => s"TableOption.CheckSum($value)" + case TableOption.Collate(value) => s"TableOption.Collate(\"$value\")" + case TableOption.Comment(value) => s"TableOption.Comment(\"$value\")" + case TableOption.Compression(value) => s"TableOption.Compression(\"$value\")" + case TableOption.Connection(value) => s"TableOption.Connection(\"$value\")" + case TableOption.Directory(str, value) => s"TableOption.Directory(\"$str\", \"$value\")" + case TableOption.DelayKeyWrite(value) => s"TableOption.DelayKeyWrite($value)" + case TableOption.Encryption(value) => s"TableOption.Encryption(\"$value\")" + case TableOption.Engine(value) => s"TableOption.Engine(\"$value\")" + case TableOption.EngineAttribute(value) => s"TableOption.EngineAttribute(\"$value\")" + case TableOption.InsertMethod(value) => s"TableOption.InsertMethod(\"$value\")" + case TableOption.KeyBlockSize(value) => s"TableOption.KeyBlockSize($value)" + case TableOption.MaxRows(value) => s"TableOption.MaxRows($value)" + case TableOption.MinRows(value) => s"TableOption.MinRows($value)" + case TableOption.PackKeys(value) => s"TableOption.PackKeys(\"$value\")" + case TableOption.RowFormat(value) => s"TableOption.RowFormat(\"$value\")" + case TableOption.SecondaryEngineAttribute(value) => s"TableOption.SecondaryEngineAttribute(\"$value\")" + case TableOption.StatsAutoRecalc(value) => s"TableOption.StatsAutoRecalc(\"$value\")" + case TableOption.StatsPersistent(value) => s"TableOption.StatsPersistent(\"$value\")" + case TableOption.StatsSamplePages(value) => s"TableOption.StatsSamplePages($value)" + case TableOption.Tablespace(name, value) => s"TableOption.Tablespace(\"$name\", \"$value\")" + case TableOption.Union(value) => s"TableOption.Union(List(${ value.map(str => s"\"$str\"").mkString(",") }))" diff --git a/module/ldbc-codegen/src/main/scala/ldbc/codegen/parser/TableParser.scala b/module/ldbc-codegen/src/main/scala/ldbc/codegen/parser/TableParser.scala index e4bdd529d..fbf8ec195 100644 --- a/module/ldbc-codegen/src/main/scala/ldbc/codegen/parser/TableParser.scala +++ b/module/ldbc-codegen/src/main/scala/ldbc/codegen/parser/TableParser.scala @@ -4,6 +4,7 @@ package ldbc.codegen.parser +import ldbc.core.TableOption import ldbc.codegen.model.* /** Parser for parsing Table definitions. @@ -21,12 +22,12 @@ trait TableParser extends KeyParser: * * SEE: https://dev.mysql.com/doc/refman/8.0/ja/innodb-tablespace-autoextend-size.html */ - private def autoextendSize: Parser[Table.Options] = + private def autoextendSize: Parser[TableOption] = customError( keyValue( caseSensitivity("autoextend_size"), """(4|8|12|16|20|24|28|32|36|40|44|48|52|56|60|64)""".r <~ caseSensitivity("m") - ) ^^ (v => Table.Options.AutoExtendSize(v.toInt)), + ) ^^ (v => TableOption.AutoExtendSize(v.toInt)), input => s""" |====================================================== |There is an error in the autoextend_size format. @@ -47,9 +48,9 @@ trait TableParser extends KeyParser: * for engines that do not support the AUTO_INCREMENT table option, insert a "dummy" row with a value one less than * the desired value after creating the table, then delete the dummy row. */ - private def autoIncrement: Parser[Table.Options] = + private def autoIncrement: Parser[TableOption] = customError( - keyValue(caseSensitivity("auto_increment"), digit) ^^ Table.Options.AutoIncrement.apply, + keyValue(caseSensitivity("auto_increment"), digit) ^^ TableOption.AutoIncrement.apply, input => s""" |====================================================== |There is an error in the auto_increment format. @@ -70,18 +71,18 @@ trait TableParser extends KeyParser: * The AVG_ROW_LENGTH option is a setting to specify the average length of a single row. This setting allows for * efficient use of database space. */ - private def avgRowLength: Parser[Table.Options] = + private def avgRowLength: Parser[TableOption] = customError( - keyValue(caseSensitivity("avg_row_length"), digit) ^^ Table.Options.AVGRowLength.apply, + keyValue(caseSensitivity("avg_row_length"), digit) ^^ TableOption.AVGRowLength.apply, failureMessage("avg_row_length", "AVG_ROW_LENGTH[=]'number'") ) /** Specifies the default character set for the table. CHARSET is a synonym for CHARACTER SET. If the character set * name is DEFAULT, the database character set is used. */ - private def characterSet: Parser[Table.Options] = + private def characterSet: Parser[TableOption] = customError( - opt(caseSensitivity("default")) ~> character ^^ Table.Options.Character.apply, + opt(caseSensitivity("default")) ~> character ^^ TableOption.Character.apply, failureMessage("character", " [DEFAULT] {CHARACTER [SET] | CHARSET} [=] 'string'") ) @@ -91,28 +92,28 @@ trait TableParser extends KeyParser: * to 0, no checksum calculation is performed, i.e., data integrity is not checked. On the other hand, when set to 1, * the checksum is calculated and data integrity is checked. */ - private def checksum: Parser[Table.Options] = + private def checksum: Parser[TableOption] = customError( keyValue(caseSensitivity("checksum"), """(0|1)""".r) ^^ { - case "0" => Table.Options.CheckSum(0) - case "1" => Table.Options.CheckSum(1) + case "0" => TableOption.CheckSum(0) + case "1" => TableOption.CheckSum(1) }, failureMessage("checksum", "CHECKSUM [=] {0 | 1}") ) /** Specifies the default collation for the table. */ - private def collateSet: Parser[Table.Options] = + private def collateSet: Parser[TableOption] = customError( - opt(caseSensitivity("default")) ~> collate ^^ Table.Options.Collate.apply, + opt(caseSensitivity("default")) ~> collate ^^ TableOption.Collate.apply, failureMessage("collate", "[DEFAULT] COLLATE[=]'string'") ) /** It is a comment of the table and can be up to 2048 characters in length. */ - private def commentOption: Parser[Table.Options] = + private def commentOption: Parser[TableOption] = customError( - keyValue(caseSensitivity("comment"), stringLiteral) ^^ Table.Options.Comment.apply, + keyValue(caseSensitivity("comment"), stringLiteral) ^^ TableOption.Comment.apply, failureMessage("comment", "COMMENT[=]'string'") ) @@ -120,15 +121,15 @@ trait TableParser extends KeyParser: * saves space. Different setting values will change the compression algorithm, resulting in different compression * speeds and efficiencies. */ - private def compression: Parser[Table.Options] = + private def compression: Parser[TableOption] = customError( keyValue( caseSensitivity("compression"), caseSensitivity("zlib") | caseSensitivity("lz4") | caseSensitivity("none") ) ^^ { - case str if str.toUpperCase == "ZLIB" => Table.Options.Compression("ZLIB") - case str if str.toUpperCase == "LZ4" => Table.Options.Compression("LZ4") - case str if str.toUpperCase == "NONE" => Table.Options.Compression("NONE") + case str if str.toUpperCase == "ZLIB" => TableOption.Compression("ZLIB") + case str if str.toUpperCase == "LZ4" => TableOption.Compression("LZ4") + case str if str.toUpperCase == "NONE" => TableOption.Compression("NONE") }, failureMessage("compression", "COMPRESSION[=]{ZLIB | LZ4 | NONE}") ) @@ -136,9 +137,9 @@ trait TableParser extends KeyParser: /** The CONNECTION option is one of the settings used by the MySQL database. It is used to configure settings related * to the connection to the database. */ - private def connection: Parser[Table.Options] = + private def connection: Parser[TableOption] = customError( - keyValue(caseSensitivity("connection"), stringLiteral) ^^ Table.Options.Connection.apply, + keyValue(caseSensitivity("connection"), stringLiteral) ^^ TableOption.Connection.apply, input => s""" |====================================================== |There is an error in the connection format. @@ -167,56 +168,59 @@ trait TableParser extends KeyParser: /** DATA DIRECTORY and INDEX DIRECTORY are options used in the MySQL database. These options are used to specify where * data and indexes are stored. */ - private def directory: Parser[Table.Options] = + private def directory: Parser[TableOption] = customError( - keyValue( - (caseSensitivity("data") | caseSensitivity("index")) ~> caseSensitivity("directory"), + (caseSensitivity("data") | caseSensitivity("index")) ~ keyValue( + caseSensitivity("directory"), stringLiteral - ) ^^ Table.Options.Directory.apply, + ) ^^ { + case str ~ value if str.toUpperCase == "DATA" => TableOption.Directory("DATA", value) + case str ~ value if str.toUpperCase == "INDEX" => TableOption.Directory("INDEX", value) + }, failureMessage("directory", "{DATA | INDEX} DIRECTORY[=]'string'") ) /** Specifies how to use delayed key writing. This applies only to MyISAM tables. Delayed key writes do not flush the * key buffer between writes. */ - private def delayKeyWrite: Parser[Table.Options] = + private def delayKeyWrite: Parser[TableOption] = customError( keyValue(caseSensitivity("delay_key_write"), """(0|1)""".r) ^^ { - case "0" => Table.Options.DelayKeyWrite(0) - case "1" => Table.Options.DelayKeyWrite(1) + case "0" => TableOption.DelayKeyWrite(0) + case "1" => TableOption.DelayKeyWrite(1) }, failureMessage("delay_key_write", "DELAY_KEY_WRITE[=]{0 | 1}") ) /** The ENCRYPTION option is one of the settings used in the MySQL database. It is used to encrypt (encode) data. */ - protected def encryption: Parser[Table.Options.Encryption] = + protected def encryption: Parser[TableOption.Encryption] = customError( keyValue(caseSensitivity("encryption"), caseSensitivity("y") | caseSensitivity("n")) ^^ { - case str if str.toUpperCase == "Y" => Table.Options.Encryption("Y") - case str if str.toUpperCase == "N" => Table.Options.Encryption("N") + case str if str.toUpperCase == "Y" => TableOption.Encryption("Y") + case str if str.toUpperCase == "N" => TableOption.Encryption("N") }, failureMessage("encryption", "ENCRYPTION[=]{Y | N}") ) /** Option to specify storage engine for table */ - private def engine: Parser[Table.Options] = + private def engine: Parser[TableOption] = customError( keyValue( caseSensitivity("engine"), "InnoDB" | "MyISAM" | "MEMORY" | "CSV" | "ARCHIVE" | "EXAMPLE" | "FEDERATED" | "HEAP" | "MERGE" | "NDB" ) ^^ { - case "InnoDB" => Table.Options.Engine("InnoDB") - case "MyISAM" => Table.Options.Engine("MyISAM") - case "MEMORY" => Table.Options.Engine("MEMORY") - case "CSV" => Table.Options.Engine("CSV") - case "ARCHIVE" => Table.Options.Engine("ARCHIVE") - case "EXAMPLE" => Table.Options.Engine("EXAMPLE") - case "FEDERATED" => Table.Options.Engine("FEDERATED") - case "HEAP" => Table.Options.Engine("HEAP") - case "MERGE" => Table.Options.Engine("MERGE") - case "NDB" => Table.Options.Engine("NDB") + case "InnoDB" => TableOption.Engine("InnoDB") + case "MyISAM" => TableOption.Engine("MyISAM") + case "MEMORY" => TableOption.Engine("MEMORY") + case "CSV" => TableOption.Engine("CSV") + case "ARCHIVE" => TableOption.Engine("ARCHIVE") + case "EXAMPLE" => TableOption.Engine("EXAMPLE") + case "FEDERATED" => TableOption.Engine("FEDERATED") + case "HEAP" => TableOption.Engine("HEAP") + case "MERGE" => TableOption.Engine("MERGE") + case "NDB" => TableOption.Engine("NDB") }, failureMessage( "engine", @@ -227,15 +231,15 @@ trait TableParser extends KeyParser: /** When inserting data into a MERGE table, INSERT_METHOD must be used to specify the table into which the rows are to * be inserted. INSERT_METHOD is a useful option only for MERGE tables. */ - private def insertMethod: Parser[Table.Options] = + private def insertMethod: Parser[TableOption] = customError( keyValue( caseSensitivity("insert_method"), caseSensitivity("NO") | caseSensitivity("FIRST") | caseSensitivity("LAST") ) ^^ { - case str if str.toUpperCase == "NO" => Table.Options.InsertMethod("NO") - case str if str.toUpperCase == "FIRST" => Table.Options.InsertMethod("FIRST") - case str if str.toUpperCase == "LAST" => Table.Options.InsertMethod("LAST") + case str if str.toUpperCase == "NO" => TableOption.InsertMethod("NO") + case str if str.toUpperCase == "FIRST" => TableOption.InsertMethod("FIRST") + case str if str.toUpperCase == "LAST" => TableOption.InsertMethod("LAST") }, failureMessage("insert_method", "INSERT_METHOD[=]{NO | FIRST | LAST}") ) @@ -243,10 +247,10 @@ trait TableParser extends KeyParser: /** The maximum number of rows you plan to store in the table. This is not a strong limit, but rather a hint to the * storage engine that the table must be able to store at least this number of rows. */ - private def maxRows: Parser[Table.Options] = + private def maxRows: Parser[TableOption] = customError( keyValue(caseSensitivity("max_rows"), """-?\d+""".r.filter(_.toLong < 4294967296L)) ^^ { digit => - Table.Options.MaxRows(digit.toLong) + TableOption.MaxRows(digit.toLong) }, failureMessage("max_rows", "MAX_ROWS[=]'size'") ) @@ -254,10 +258,10 @@ trait TableParser extends KeyParser: /** The minimum number of rows you plan to store in the table. MEMORY The storage engine uses this option as a hint * regarding memory usage. */ - private def minRows: Parser[Table.Options] = + private def minRows: Parser[TableOption] = customError( keyValue(caseSensitivity("min_rows"), """-?\d+""".r) ^^ { digit => - Table.Options.MinRows(digit.toLong) + TableOption.MinRows(digit.toLong) }, failureMessage("min_rows", "MIN_ROWS[=]'size'") ) @@ -266,22 +270,22 @@ trait TableParser extends KeyParser: * faster reads. Setting this option to 0 disables all packing of keys. Setting it to DEFAULT tells the storage * engine to pack only long CHAR, VARCHAR, BINARY, or VARBINARY columns. */ - private def packKeys: Parser[Table.Options] = + private def packKeys: Parser[TableOption] = customError( keyValue( caseSensitivity("pack_keys"), "0" | "1" | caseSensitivity("default") ) ^^ { - case "0" => Table.Options.PackKeys("0") - case "1" => Table.Options.PackKeys("1") - case str if str.toUpperCase == "DEFAULT" => Table.Options.PackKeys("DEFAULT") + case "0" => TableOption.PackKeys("0") + case "1" => TableOption.PackKeys("1") + case str if str.toUpperCase == "DEFAULT" => TableOption.PackKeys("DEFAULT") }, failureMessage("pack_keys", "PACK_KEYS[=]{0 | 1 | DEFAULT}") ) /** Defines the physical format in which the rows will be stored. */ - private def rowFormat: Parser[Table.Options] = + private def rowFormat: Parser[TableOption] = customError( keyValue( caseSensitivity("row_format"), @@ -289,12 +293,12 @@ trait TableParser extends KeyParser: "redundant" ) | caseSensitivity("compact") ) ^^ { - case str if str.toUpperCase == "DEFAULT" => Table.Options.RowFormat("DEFAULT") - case str if str.toUpperCase == "DYNAMIC" => Table.Options.RowFormat("DYNAMIC") - case str if str.toUpperCase == "FIXED" => Table.Options.RowFormat("FIXED") - case str if str.toUpperCase == "COMPRESSED" => Table.Options.RowFormat("COMPRESSED") - case str if str.toUpperCase == "REDUNDANT" => Table.Options.RowFormat("REDUNDANT") - case str if str.toUpperCase == "COMPACT" => Table.Options.RowFormat("COMPACT") + case str if str.toUpperCase == "DEFAULT" => TableOption.RowFormat("DEFAULT") + case str if str.toUpperCase == "DYNAMIC" => TableOption.RowFormat("DYNAMIC") + case str if str.toUpperCase == "FIXED" => TableOption.RowFormat("FIXED") + case str if str.toUpperCase == "COMPRESSED" => TableOption.RowFormat("COMPRESSED") + case str if str.toUpperCase == "REDUNDANT" => TableOption.RowFormat("REDUNDANT") + case str if str.toUpperCase == "COMPACT" => TableOption.RowFormat("COMPACT") }, failureMessage("row_format", "ROW_FORMAT[=]{DEFAULT | DYNAMIC | FIXED | COMPRESSED | REDUNDANT | COMPACT}") ) @@ -305,15 +309,15 @@ trait TableParser extends KeyParser: * changed. A value of 0 prevents automatic recalculation of this table. With this setting, to recalculate the * statistics after making significant changes to the table, issue an ANALYZE TABLE statement. */ - private def statsAutoRecalc: Parser[Table.Options] = + private def statsAutoRecalc: Parser[TableOption] = customError( keyValue( caseSensitivity("stats_auto_recalc"), "0" | "1" | caseSensitivity("default") ) ^^ { - case "0" => Table.Options.StatsAutoRecalc("0") - case "1" => Table.Options.StatsAutoRecalc("1") - case str if str.toUpperCase == "DEFAULT" => Table.Options.StatsAutoRecalc("DEFAULT") + case "0" => TableOption.StatsAutoRecalc("0") + case "1" => TableOption.StatsAutoRecalc("1") + case str if str.toUpperCase == "DEFAULT" => TableOption.StatsAutoRecalc("DEFAULT") }, failureMessage("stats_auto_recalc", "STATS_AUTO_RECALC[=]{0 | 1 | DEFAULT}") ) @@ -322,15 +326,15 @@ trait TableParser extends KeyParser: * statistics setting is determined by the innodb_stats_persistent configuration option. A value of 1 enables * persistent statistics for the table, while a value of 0 disables this feature. */ - private def statsPersistent: Parser[Table.Options] = + private def statsPersistent: Parser[TableOption] = customError( keyValue( caseSensitivity("stats_persistent"), "0" | "1" | caseSensitivity("default") ) ^^ { - case "0" => Table.Options.StatsPersistent("0") - case "1" => Table.Options.StatsPersistent("1") - case str if str.toUpperCase == "DEFAULT" => Table.Options.StatsPersistent("DEFAULT") + case "0" => TableOption.StatsPersistent("0") + case "1" => TableOption.StatsPersistent("1") + case str if str.toUpperCase == "DEFAULT" => TableOption.StatsPersistent("DEFAULT") }, failureMessage("stats_persistent", "STATS_PERSISTENT[=]{0 | 1 | DEFAULT}") ) @@ -338,22 +342,22 @@ trait TableParser extends KeyParser: /** Number of index pages to sample when estimating cardinality and other statistics (such as those computed by * ANALYZE TABLE) for indexed columns. */ - private def statsSamplePages: Parser[Table.Options] = + private def statsSamplePages: Parser[TableOption] = customError( - keyValue(caseSensitivity("stats_sample_pages"), digit) ^^ Table.Options.StatsSamplePages.apply, + keyValue(caseSensitivity("stats_sample_pages"), digit) ^^ TableOption.StatsSamplePages.apply, failureMessage("stats_sample_pages", "STATS_SAMPLE_PAGES[=]'size'") ) /** The TABLESPACE clause can be used to create tables in an existing general tablespace, file-per-table tablespace, * or system tablespace. */ - private def tablespace: Parser[Table.Options] = + private def tablespace: Parser[TableOption] = customError( caseSensitivity("tablespace") ~> sqlIdent ~ opt( caseSensitivity("storage") ~> caseSensitivity("disk") | caseSensitivity("memory") ) ^^ { case name ~ storage => - Table.Options.Tablespace( + TableOption.Tablespace( name, storage.map { case "DISK" => "DISK" @@ -366,21 +370,27 @@ trait TableParser extends KeyParser: /** Used to access collections of identical MyISAM tables. This only works with MERGE tables. */ - private def union: Parser[Table.Options] = + private def union: Parser[TableOption] = customError( - keyValue(caseSensitivity("union"), "(" ~> repsep(sqlIdent, ",") <~ ")") ^^ Table.Options.Union.apply, + keyValue(caseSensitivity("union"), "(" ~> repsep(sqlIdent, ",") <~ ")") ^^ TableOption.Union.apply, failureMessage("union", "UNION[=](table_name, table_name, ...)") ) - private def tableOption: Parser[Table.Options] = + private def engineAttributeOption: Parser[TableOption] = + engineAttribute ^^ { v => TableOption.EngineAttribute(v.value) } + + private def keyBlockSizeOption: Parser[TableOption] = + keyBlockSize ^^ { v => TableOption.KeyBlockSize(v.value) } + + private def tableOption: Parser[TableOption] = autoextendSize | autoIncrement | avgRowLength | characterSet | checksum | collateSet | commentOption | compression | connection | directory | delayKeyWrite | encryption | - engine | engineAttribute ^^ Table.Options.EngineAttribute.apply | insertMethod | - keyBlockSize ^^ Table.Options.KeyBlockSize.apply | maxRows | minRows | packKeys | + engine | engineAttributeOption | insertMethod | + keyBlockSizeOption | maxRows | minRows | packKeys | rowFormat | keyValue( caseSensitivity("secondary_engine_attribute"), sqlIdent - ) ^^ Table.Options.SecondaryEngineAttribute.apply | + ) ^^ TableOption.SecondaryEngineAttribute.apply | statsAutoRecalc | statsPersistent | statsSamplePages | tablespace | union protected def tableStatements: Parser[Table.CreateStatement | Table.DropStatement] = diff --git a/module/ldbc-schemaspy/src/main/scala/ldbc/schemaspy/builder/TableBuilder.scala b/module/ldbc-schemaspy/src/main/scala/ldbc/schemaspy/builder/TableBuilder.scala index 7efcbc43a..3e0e4c659 100644 --- a/module/ldbc-schemaspy/src/main/scala/ldbc/schemaspy/builder/TableBuilder.scala +++ b/module/ldbc-schemaspy/src/main/scala/ldbc/schemaspy/builder/TableBuilder.scala @@ -24,7 +24,12 @@ import ldbc.core.validator.TableValidator */ case class TableBuilder(db: SchemaspyDatabase, table: Table[?]) extends TableValidator: - private val schemaTable = new SchemaspyTable(db, null, db.getSchema.getName, table._name, table.comment.orNull) + private val comment = table.options.flatMap { + case comment: TableOption.Comment => Some(comment.value) + case _ => None + }.headOption + + private val schemaTable = new SchemaspyTable(db, null, db.getSchema.getName, table._name, comment.orNull) /** A method to extract columns with PrimaryKey set from all columns and keys set in the Table. *