diff --git a/bin/mysql-schema-petstore.sh b/bin/mysql-schema-petstore.sh new file mode 100644 index 00000000000..157a36dd243 --- /dev/null +++ b/bin/mysql-schema-petstore.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +SCRIPT="$0" +echo "# START SCRIPT: $SCRIPT" + +while [ -h "$SCRIPT" ] ; do + ls=`ls -ld "$SCRIPT"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + SCRIPT="$link" + else + SCRIPT=`dirname "$SCRIPT"`/"$link" + fi +done + +if [ ! -d "${APP_DIR}" ]; then + APP_DIR=`dirname "$SCRIPT"`/.. + APP_DIR=`cd "${APP_DIR}"; pwd` +fi + +executable="./modules/openapi-generator-cli/target/openapi-generator-cli.jar" + +if [ ! -f "$executable" ] +then + mvn -B clean package +fi + +# if you've executed sbt assembly previously it will use that instead. +export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties" +ags="generate -t modules/openapi-generator/src/main/resources/mysql-schema -i modules/openapi-generator/src/test/resources/2_0/petstore-with-fake-endpoints-models-for-testing.yaml -g mysql-schema -o samples/schema/petstore/mysql $@" + +java $JAVA_OPTS -jar $executable $ags diff --git a/bin/security/mysql-schema-petstore.sh b/bin/security/mysql-schema-petstore.sh new file mode 100644 index 00000000000..ebdda35fe05 --- /dev/null +++ b/bin/security/mysql-schema-petstore.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +SCRIPT="$0" +echo "# START SCRIPT: $SCRIPT" + +while [ -h "$SCRIPT" ] ; do + ls=`ls -ld "$SCRIPT"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + SCRIPT="$link" + else + SCRIPT=`dirname "$SCRIPT"`/"$link" + fi +done + +if [ ! -d "${APP_DIR}" ]; then + APP_DIR=`dirname "$SCRIPT"`/.. + APP_DIR=`cd "${APP_DIR}"; pwd` +fi + +executable="./modules/openapi-generator-cli/target/openapi-generator-cli.jar" + +if [ ! -f "$executable" ] +then + mvn clean package +fi + +# if you've executed sbt assembly previously it will use that instead. +export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties" +ags="generate -t modules/openapi-generator/src/main/resources/mysql-schema -i modules/openapi-generator/src/test/resources/2_0/petstore-security-test.yaml -g mysql-schema -o samples/schema/petstore-security-test/mysql $@" + +java $JAVA_OPTS -jar $executable $ags diff --git a/bin/utils/ensure-up-to-date b/bin/utils/ensure-up-to-date index 1580f694274..486f1127587 100755 --- a/bin/utils/ensure-up-to-date +++ b/bin/utils/ensure-up-to-date @@ -18,6 +18,7 @@ sleep 5 ./bin/kotlin-client-string.sh > /dev/null 2>&1 ./bin/kotlin-client-threetenbp.sh > /dev/null 2>&1 ./bin/kotlin-server-petstore.sh > /dev/null 2>&1 +./bin/mysql-schema-petstore.sh > /dev/null 2>&1 ./bin/php-petstore.sh > /dev/null 2>&1 ./bin/php-silex-petstore-server.sh > /dev/null 2>&1 ./bin/php-symfony-petstore.sh > /dev/null 2>&1 diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenType.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenType.java index 958fabc2b85..dda89fe50c0 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenType.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenType.java @@ -25,7 +25,7 @@ import java.util.Locale; import java.util.Map; public enum CodegenType { - CLIENT, SERVER, DOCUMENTATION, CONFIG, OTHER; + CLIENT, SERVER, DOCUMENTATION, SCHEMA, CONFIG, OTHER; private static Map names = new HashMap(); @@ -49,6 +49,7 @@ public enum CodegenType { names.put("client", CLIENT); names.put("server", SERVER); names.put("documentation", DOCUMENTATION); + names.put("schema", SCHEMA); names.put("other", OTHER); } } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/MysqlSchemaCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/MysqlSchemaCodegen.java new file mode 100644 index 00000000000..73ac7e6f33e --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/MysqlSchemaCodegen.java @@ -0,0 +1,1071 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openapitools.codegen.languages; + +import org.openapitools.codegen.CliOption; +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.CodegenOperation; +import org.openapitools.codegen.CodegenType; +import org.openapitools.codegen.SupportingFile; +import org.openapitools.codegen.DefaultCodegen; +import org.openapitools.codegen.CodegenConfig; +import org.openapitools.codegen.CodegenModel; +import org.openapitools.codegen.CodegenProperty; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Vector; +import java.util.Map; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.Locale; + +public class MysqlSchemaCodegen extends DefaultCodegen implements CodegenConfig { + private static final Logger LOGGER = LoggerFactory.getLogger(MysqlSchemaCodegen.class); + + public static final String CODEGEN_VENDOR_EXTENSION_KEY = "x-mysqlSchema"; + public static final String DEFAULT_DATABASE_NAME = "defaultDatabaseName"; + public static final String JSON_DATA_TYPE_ENABLED = "jsonDataTypeEnabled"; + public static final Integer ENUM_MAX_ELEMENTS = 65535; + public static final Integer IDENTIFIER_MAX_LENGTH = 64; + + protected Vector mysqlNumericTypes = new Vector(Arrays.asList( + "BIGINT", "BIT", "BOOL", "BOOLEAN", "DEC", "DECIMAL", "DOUBLE", "DOUBLE PRECISION", "FIXED", "FLOAT", "INT", "INTEGER", "MEDIUMINT", "NUMERIC", "REAL", "SMALLINT", "TINYINT" + )); + + protected Vector mysqlDateAndTimeTypes = new Vector(Arrays.asList( + "DATE", "DATETIME", "TIME", "TIMESTAMP", "YEAR" + )); + + protected Vector mysqlStringTypes = new Vector(Arrays.asList( + "BINARY", "BLOB", "CHAR", "CHAR BYTE", "CHARACTER", "ENUM", "LONGBLOB", "LONGTEXT", "MEDIUMBLOB", "MEDIUMTEXT", "SET", "TEXT", "TINYBLOB", "TINYTEXT", "VARBINARY", "VARCHAR" + )); + + protected Vector mysqlSpatialTypes = new Vector(Arrays.asList( + "GEOMETRY", "GEOMETRYCOLLECTION", "LINESTRING", "MULTILINESTRING", "MULTIPOINT", "MULTIPOLYGON", "POINT", "POLYGON" + )); + + protected String defaultDatabaseName = "", databaseNamePrefix = "", databaseNameSuffix = "_db"; + protected String tableNamePrefix = "tbl_", tableNameSuffix = ""; + protected String columnNamePrefix = "col_", columnNameSuffix = ""; + protected Boolean jsonDataTypeEnabled = true; + + public MysqlSchemaCodegen() { + super(); + + // clear import mapping (from default generator) as mysql does not use import directives + importMapping.clear(); + + //modelTestTemplateFiles.put("model_test.mustache", ".php"); + // no doc files + // modelDocTemplateFiles.clear(); + // apiDocTemplateFiles.clear(); + + // https://dev.mysql.com/doc/refman/8.0/en/keywords.html + setReservedWordsLowerCase( + Arrays.asList( + // SQL reserved words + "ACCESSIBLE", "ADD", "ALL", "ALTER", "ANALYZE", "AND", "AS", "ASC", "ASENSITIVE", + "BEFORE", "BETWEEN", "BIGINT", "BINARY", "BLOB", "BOTH", "BY", + "CALL", "CASCADE", "CASE", "CHANGE", "CHAR", "CHARACTER", "CHECK", "COLLATE", "COLUMN", "CONDITION", "CONSTRAINT", "CONTINUE", "CONVERT", "CREATE", "CROSS", "CUBE", "CUME_DIST", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", "CURRENT_USER", "CURSOR", + "DATABASE", "DATABASES", "DAY_HOUR", "DAY_MICROSECOND", "DAY_MINUTE", "DAY_SECOND", "DEC", "DECIMAL", "DECLARE", "DEFAULT", "DELAYED", "DELETE", "DENSE_RANK", "DESC", "DESCRIBE", "DETERMINISTIC", "DISTINCT", "DISTINCTROW", "DIV", "DOUBLE", "DROP", "DUAL", + "EACH", "ELSE", "ELSEIF", "EMPTY", "ENCLOSED", "ESCAPED", "EXCEPT", "EXISTS", "EXIT", "EXPLAIN", + "FALSE", "FETCH", "FIRST_VALUE", "FLOAT", "FLOAT4", "FLOAT8", "FOR", "FORCE", "FOREIGN", "FROM", "FULLTEXT", "FUNCTION", + "GENERATED", "GET", "GRANT", "GROUP", "GROUPING", "GROUPS", + "HAVING", "HIGH_PRIORITY", "HOUR_MICROSECOND", "HOUR_MINUTE", "HOUR_SECOND", + "IF", "IGNORE", "IN", "INDEX", "INFILE", "INNER", "INOUT", "INSENSITIVE", "INSERT", "INT", "INT1", "INT2", "INT3", "INT4", "INT8", "INTEGER", "INTERVAL", "INTO", "IO_AFTER_GTIDS", "IO_BEFORE_GTIDS", "IS", "ITERATE", + "JOIN", "JSON_TABLE", + "KEY", "KEYS", "KILL", + "LAG", "LAST_VALUE", "LEAD", "LEADING", "LEAVE", "LEFT", "LIKE", "LIMIT", "LINEAR", "LINES", "LOAD", "LOCALTIME", "LOCALTIMESTAMP", "LOCK", "LONG", "LONGBLOB", "LONGTEXT", "LOOP", "LOW_PRIORITY", + "MASTER_BIND", "MASTER_SSL_VERIFY_SERVER_CERT", "MATCH", "MAXVALUE", "MEDIUMBLOB", "MEDIUMINT", "MEDIUMTEXT", "MIDDLEINT", "MINUTE_MICROSECOND", "MINUTE_SECOND", "MOD", "MODIFIES", + "NATURAL", "NOT", "NO_WRITE_TO_BINLOG", "NTH_VALUE", "NTILE", "NULL", "NUMERIC", + "OF", "ON", "OPTIMIZE", "OPTIMIZER_COSTS", "OPTION", "OPTIONALLY", "OR", "ORDER", "OUT", "OUTER", "OUTFILE", "OVER", + "PARTITION", "PERCENT_RANK", "PERSIST", "PERSIST_ONLY", "PRECISION", "PRIMARY", "PROCEDURE", "PURGE", + "RANGE", "RANK", "READ", "READS", "READ_WRITE", "REAL", "RECURSIVE", "REFERENCES", "REGEXP", "RELEASE", "RENAME", "REPEAT", "REPLACE", "REQUIRE", "RESIGNAL", "RESTRICT", "RETURN", "REVOKE", "RIGHT", "RLIKE", "ROLE", "ROW", "ROWS", "ROW_NUMBER", + "SCHEMA", "SCHEMAS", "SECOND_MICROSECOND", "SELECT", "SENSITIVE", "SEPARATOR", "SET", "SHOW", "SIGNAL", "SMALLINT", "SPATIAL", "SPECIFIC", "SQL", "SQLEXCEPTION", "SQLSTATE", "SQLWARNING", "SQL_BIG_RESULT", "SQL_CALC_FOUND_ROWS", "SQL_SMALL_RESULT", "SSL", "STARTING", "STORED", "STRAIGHT_JOIN", "SYSTEM", + "TABLE", "TERMINATED", "THEN", "TINYBLOB", "TINYINT", "TINYTEXT", "TO", "TRAILING", "TRIGGER", "TRUE", + "UNDO", "UNION", "UNIQUE", "UNLOCK", "UNSIGNED", "UPDATE", "USAGE", "USE", "USING", "UTC_DATE", "UTC_TIME", "UTC_TIMESTAMP", + "VALUES", "VARBINARY", "VARCHAR", "VARCHARACTER", "VARYING", "VIRTUAL", + "WHEN", "WHERE", "WHILE", "WINDOW", "WITH", "WRITE", + "XOR", + "YEAR_MONTH", + "ZEROFILL" + ) + ); + + // all types can be threaded as primitives except array, object and refs + languageSpecificPrimitives = new HashSet( + Arrays.asList( + "bool", + "boolean", + "int", + "integer", + "double", + "float", + "string", + "date", + "Date", + "DateTime", + "long", + "short", + "char", + "ByteArray", + "binary", + "file", + "UUID", + "BigDecimal", + "mixed", + "number", + "void", + "byte" + ) + ); + + // https://dev.mysql.com/doc/refman/8.0/en/data-types.html + typeMapping.put("array", "JSON"); + typeMapping.put("map", "JSON"); + typeMapping.put("List", "JSON"); + typeMapping.put("boolean", "BOOL"); + typeMapping.put("string", "TEXT"); + typeMapping.put("int", "INT"); + typeMapping.put("byte", "TEXT"); + typeMapping.put("float", "DECIMAL"); + typeMapping.put("number", "DECIMAL"); + typeMapping.put("date", "DATE"); + typeMapping.put("Date", "DATETIME"); + typeMapping.put("DateTime", "DATETIME"); + typeMapping.put("long", "BIGINT"); + typeMapping.put("short", "SMALLINT"); + typeMapping.put("char", "TEXT"); + typeMapping.put("double", "DECIMAL"); + typeMapping.put("object", "JSON"); + typeMapping.put("integer", "INT"); + typeMapping.put("ByteArray", "MEDIUMBLOB"); + typeMapping.put("binary", "MEDIUMBLOB"); + typeMapping.put("file", "MEDIUMBLOB"); + typeMapping.put("UUID", "TEXT"); + typeMapping.put("BigDecimal", "DECIMAL"); + + embeddedTemplateDir = templateDir = "mysql-schema"; + + // it seems that cli options from DefaultCodegen are useless here + cliOptions.clear(); + addOption(DEFAULT_DATABASE_NAME, "Default database name for all MySQL queries", defaultDatabaseName); + addSwitch(JSON_DATA_TYPE_ENABLED, "Use special JSON MySQL data type for complex model properties. Requires MySQL version 5.7.8. Generates TEXT data type when disabled", jsonDataTypeEnabled); + } + + @Override + public CodegenType getTag() { + return CodegenType.SCHEMA; + } + + @Override + public String getName() { + return "mysql-schema"; + } + + @Override + public String getHelp() { + return "Generates a MySQL schema based on the model or schema defined in the OpenAPI specification (v2, v3)."; + } + + @Override + public void processOpts() { + super.processOpts(); + + if (additionalProperties.containsKey(DEFAULT_DATABASE_NAME)) { + if (additionalProperties.get(DEFAULT_DATABASE_NAME).equals("")) { + additionalProperties.remove(DEFAULT_DATABASE_NAME); + } else { + this.setDefaultDatabaseName((String) additionalProperties.get(DEFAULT_DATABASE_NAME)); + // default database name may be escaped, need to overwrite additional prop + additionalProperties.put(DEFAULT_DATABASE_NAME, getDefaultDatabaseName()); + } + } + + if (additionalProperties.containsKey(JSON_DATA_TYPE_ENABLED)) { + this.setJsonDataTypeEnabled(Boolean.valueOf(additionalProperties.get(JSON_DATA_TYPE_ENABLED).toString())); + } else { + additionalProperties.put(JSON_DATA_TYPE_ENABLED, getJsonDataTypeEnabled()); + } + + supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); + supportingFiles.add(new SupportingFile("mysql_schema.mustache", "", "mysql_schema.sql")); + } + + @Override + public Map postProcessModels(Map objs) { + objs = super.postProcessModels(objs); + + List models = (List) objs.get("models"); + for (Object _mo : models) { + Map mo = (Map) _mo; + CodegenModel model = (CodegenModel) mo.get("model"); + String modelName = model.getName(); + String modelDescription = model.getDescription(); + Map modelVendorExtensions = model.getVendorExtensions(); + Map mysqlSchema = new HashMap(); + Map tableDefinition = new HashMap(); + + if (modelVendorExtensions.containsKey(CODEGEN_VENDOR_EXTENSION_KEY)) { + // user already specified schema values + LOGGER.info("Found vendor extension in '" + modelName + "' model, autogeneration skipped"); + continue; + } else { + modelVendorExtensions.put(CODEGEN_VENDOR_EXTENSION_KEY, mysqlSchema); + mysqlSchema.put("tableDefinition", tableDefinition); + tableDefinition.put("tblName", toTableName(modelName)); + tableDefinition.put("tblComment", modelDescription); + } + } + + return objs; + } + + @Override + public void postProcessModelProperty(CodegenModel model, CodegenProperty property) { + switch (property.getDataType().toUpperCase(Locale.ROOT)) { + case "BOOL": + processBooleanTypeProperty(model, property); + break; + case "TINYINT": + case "SMALLINT": + case "INT": + case "BIGINT": + processIntegerTypeProperty(model, property); + break; + case "DECIMAL": + processDecimalTypeProperty(model, property); + break; + case "MEDIUMBLOB": + case "TEXT": + processStringTypeProperty(model, property); + break; + case "DATE": + case "DATETIME": + processDateTypeProperty(model, property); + break; + case "JSON": + processJsonTypeProperty(model, property); + break; + default: + processUnknownTypeProperty(model, property); + } + } + + /** + * Processes each model's property mapped to integer type and adds related vendor extensions + * + * @param model model + * @param property model's property + */ + public void processIntegerTypeProperty(CodegenModel model, CodegenProperty property) { + Map vendorExtensions = property.getVendorExtensions(); + Map mysqlSchema = new HashMap(); + Map columnDefinition = new HashMap(); + ArrayList columnDataTypeArguments = new ArrayList(); + String baseName = property.getBaseName(); + String dataType = property.getDataType(); + String dataFormat = property.getDataFormat(); + String description = property.getDescription(); + String minimum = property.getMinimum(); + String maximum = property.getMaximum(); + Boolean exclusiveMinimum = property.getExclusiveMinimum(); + Boolean exclusiveMaximum = property.getIExclusiveMaximum(); + String defaultValue = property.getDefaultValue(); + Boolean required = property.getRequired(); + Boolean unsigned = false; + Boolean isUuid = property.isUuid; + Boolean isEnum = property.isEnum; + + if (vendorExtensions.containsKey(CODEGEN_VENDOR_EXTENSION_KEY)) { + // user already specified schema values + LOGGER.info("Found vendor extension in '" + baseName + "' property, autogeneration skipped"); + return; + } + + vendorExtensions.put(CODEGEN_VENDOR_EXTENSION_KEY, mysqlSchema); + mysqlSchema.put("columnDefinition", columnDefinition); + columnDefinition.put("colName", toColumnName(baseName)); + + if (Boolean.TRUE.equals(isEnum)) { + Map allowableValues = property.getAllowableValues(); + List enumValues = (List) allowableValues.get("values"); + for (Integer i = 0; i< enumValues.size(); i++) { + if (i > ENUM_MAX_ELEMENTS - 1) { + LOGGER.warn("ENUM column can have maximum of " + ENUM_MAX_ELEMENTS.toString() + " distinct elements, following value will be skipped: " + (String) enumValues.get(i)); + break; + } + String value = String.valueOf(enumValues.get(i)); + columnDataTypeArguments.add(toCodegenMysqlDataTypeArgument(value, (Boolean) (i + 1 < enumValues.size()))); + } + columnDefinition.put("colDataType", "ENUM"); + columnDefinition.put("colDataTypeArguments", columnDataTypeArguments); + } else { + if (dataFormat == "int64") { + columnDefinition.put("colDataType", "BIGINT"); + } else { + Long min = (minimum != null) ? Long.parseLong(minimum) : null; + Long max = (maximum != null) ? Long.parseLong(maximum) : null; + if (exclusiveMinimum == true && min != null) min += 1; + if (exclusiveMaximum == true && max != null) max -= 1; + if (min != null && min >= 0) { + unsigned = true; + } + columnDefinition.put("colUnsigned", unsigned); + columnDefinition.put("colDataType", getMysqlMatchedIntegerDataType(min, max, unsigned)); + } + } + + if (Boolean.TRUE.equals(required)) { + columnDefinition.put("colNotNull", true); + } else { + columnDefinition.put("colNotNull", false); + try { + columnDefinition.put("colDefault", toCodegenMysqlDataTypeDefault(defaultValue, (String) columnDefinition.get("colDataType"))); + } catch (RuntimeException exception) { + LOGGER.warn("Property '" + baseName + "' of model '" + model.getName() + "' mapped to MySQL data type which doesn't support default value"); + columnDefinition.put("colDefault", null); + } + } + + if (description != null) { + columnDefinition.put("colComment", description); + } + } + + /** + * Processes each model's property mapped to decimal type and adds related vendor extensions + * + * @param model model + * @param property model's property + */ + public void processDecimalTypeProperty(CodegenModel model, CodegenProperty property) { + Map vendorExtensions = property.getVendorExtensions(); + Map mysqlSchema = new HashMap(); + Map columnDefinition = new HashMap(); + ArrayList columnDataTypeArguments = new ArrayList(); + String baseName = property.getBaseName(); + String dataType = property.getDataType(); + String dataFormat = property.getDataFormat(); + String description = property.getDescription(); + String minimum = property.getMinimum(); + String maximum = property.getMaximum(); + Boolean exclusiveMinimum = property.getExclusiveMinimum(); + Boolean exclusiveMaximum = property.getIExclusiveMaximum(); + String defaultValue = property.getDefaultValue(); + Boolean required = property.getRequired(); + Boolean unsigned = false; + Boolean isEnum = property.isEnum; + + if (vendorExtensions.containsKey(CODEGEN_VENDOR_EXTENSION_KEY)) { + // user already specified schema values + LOGGER.info("Found vendor extension in '" + baseName + "' property, autogeneration skipped"); + return; + } + + vendorExtensions.put(CODEGEN_VENDOR_EXTENSION_KEY, mysqlSchema); + mysqlSchema.put("columnDefinition", columnDefinition); + columnDefinition.put("colName", toColumnName(baseName)); + + if (Boolean.TRUE.equals(isEnum)) { + Map allowableValues = property.getAllowableValues(); + List enumValues = (List) allowableValues.get("values"); + for (Integer i = 0; i< enumValues.size(); i++) { + if (i > ENUM_MAX_ELEMENTS - 1) { + LOGGER.warn("ENUM column can have maximum of " + ENUM_MAX_ELEMENTS.toString() + " distinct elements, following value will be skipped: " + (String) enumValues.get(i)); + break; + } + String value = String.valueOf(enumValues.get(i)); + columnDataTypeArguments.add(toCodegenMysqlDataTypeArgument(value, (Boolean) (i + 1 < enumValues.size()))); + } + columnDefinition.put("colDataType", "ENUM"); + columnDefinition.put("colDataTypeArguments", columnDataTypeArguments); + } else { + Float min = (minimum != null) ? Float.valueOf(minimum) : null; + Float max = (maximum != null) ? Float.valueOf(maximum) : null; + if (exclusiveMinimum == true && min != null) min += 1; + if (exclusiveMaximum == true && max != null) max -= 1; + if (min != null && min >= 0) { + unsigned = true; + } + columnDefinition.put("colDataType", "DECIMAL"); + columnDefinition.put("colUnsigned", unsigned); + columnDefinition.put("colDataTypeArguments", columnDataTypeArguments); + columnDataTypeArguments.add(toCodegenMysqlDataTypeArgument(20, true)); + columnDataTypeArguments.add(toCodegenMysqlDataTypeArgument(9, false)); + } + + if (Boolean.TRUE.equals(required)) { + columnDefinition.put("colNotNull", true); + } else { + columnDefinition.put("colNotNull", false); + try { + columnDefinition.put("colDefault", toCodegenMysqlDataTypeDefault(defaultValue, (String) columnDefinition.get("colDataType"))); + } catch (RuntimeException exception) { + LOGGER.warn("Property '" + baseName + "' of model '" + model.getName() + "' mapped to MySQL data type which doesn't support default value"); + columnDefinition.put("colDefault", null); + } + } + + if (description != null) { + columnDefinition.put("colComment", description); + } + } + + /** + * Processes each model's property mapped to boolean type and adds related vendor extensions + * + * @param model model + * @param property model's property + */ + public void processBooleanTypeProperty(CodegenModel model, CodegenProperty property) { + Map vendorExtensions = property.getVendorExtensions(); + Map mysqlSchema = new HashMap(); + Map columnDefinition = new HashMap(); + ArrayList columnDataTypeArguments = new ArrayList(); + String baseName = property.getBaseName(); + String description = property.getDescription(); + String defaultValue = property.getDefaultValue(); + Boolean required = property.getRequired(); + + if (vendorExtensions.containsKey(CODEGEN_VENDOR_EXTENSION_KEY)) { + // user already specified schema values + LOGGER.info("Found vendor extension in '" + baseName + "' property, autogeneration skipped"); + return; + } + + vendorExtensions.put(CODEGEN_VENDOR_EXTENSION_KEY, mysqlSchema); + mysqlSchema.put("columnDefinition", columnDefinition); + columnDefinition.put("colName", toColumnName(baseName)); + columnDefinition.put("colDataType", "TINYINT"); + columnDefinition.put("colDataTypeArguments", columnDataTypeArguments); + columnDataTypeArguments.add(toCodegenMysqlDataTypeArgument(1, false)); + + if (Boolean.TRUE.equals(required)) { + columnDefinition.put("colNotNull", true); + } else { + columnDefinition.put("colNotNull", false); + try { + columnDefinition.put("colDefault", toCodegenMysqlDataTypeDefault(defaultValue, (String) columnDefinition.get("colDataType"))); + } catch (RuntimeException exception) { + LOGGER.warn("Property '" + baseName + "' of model '" + model.getName() + "' mapped to MySQL data type which doesn't support default value"); + columnDefinition.put("colDefault", null); + } + } + + if (description != null) { + columnDefinition.put("colComment", description); + } + } + + /** + * Processes each model's property mapped to string type and adds related vendor extensions + * + * @param model model + * @param property model's property + */ + public void processStringTypeProperty(CodegenModel model, CodegenProperty property) { + Map vendorExtensions = property.getVendorExtensions(); + Map mysqlSchema = new HashMap(); + Map columnDefinition = new HashMap(); + ArrayList columnDataTypeArguments = new ArrayList(); + String baseName = property.getBaseName(); + String dataType = property.getDataType(); + String dataFormat = property.getDataFormat(); + String description = property.getDescription(); + Integer minLength = property.getMinLength(); + Integer maxLength = property.getMaxLength(); + String defaultValue = property.getDefaultValue(); + Boolean required = property.getRequired(); + Boolean isEnum = property.isEnum; + + if (vendorExtensions.containsKey(CODEGEN_VENDOR_EXTENSION_KEY)) { + // user already specified schema values + LOGGER.info("Found vendor extension in '" + baseName + "' property, autogeneration skipped"); + return; + } + + vendorExtensions.put(CODEGEN_VENDOR_EXTENSION_KEY, mysqlSchema); + mysqlSchema.put("columnDefinition", columnDefinition); + columnDefinition.put("colName", toColumnName(baseName)); + + if (Boolean.TRUE.equals(isEnum)) { + Map allowableValues = property.getAllowableValues(); + List enumValues = (List) allowableValues.get("values"); + columnDefinition.put("colDataType", "ENUM"); + columnDefinition.put("colDataTypeArguments", columnDataTypeArguments); + for (Integer i = 0; i< enumValues.size(); i++) { + if (i > ENUM_MAX_ELEMENTS - 1) { + LOGGER.warn("ENUM column can have maximum of " + ENUM_MAX_ELEMENTS.toString() + " distinct elements, following value will be skipped: " + (String) enumValues.get(i)); + break; + } + String value = String.valueOf(enumValues.get(i)); + columnDataTypeArguments.add(toCodegenMysqlDataTypeArgument(value, (Boolean) (i + 1 < enumValues.size()))); + } + } else if (dataType.equals("MEDIUMBLOB")){ + columnDefinition.put("colDataType", "MEDIUMBLOB"); + } else { + String matchedStringType = getMysqlMatchedStringDataType(minLength, maxLength); + columnDefinition.put("colDataType", matchedStringType); + if (matchedStringType.equals("CHAR") || matchedStringType.equals("VARCHAR")) { + columnDefinition.put("colDataTypeArguments", columnDataTypeArguments); + columnDataTypeArguments.add(toCodegenMysqlDataTypeArgument((maxLength != null) ? maxLength : 255, false)); + } + } + + if (Boolean.TRUE.equals(required)) { + columnDefinition.put("colNotNull", true); + } else { + columnDefinition.put("colNotNull", false); + try { + columnDefinition.put("colDefault", toCodegenMysqlDataTypeDefault(defaultValue, (String) columnDefinition.get("colDataType"))); + } catch (RuntimeException exception) { + LOGGER.warn("Property '" + baseName + "' of model '" + model.getName() + "' mapped to MySQL data type which doesn't support default value"); + columnDefinition.put("colDefault", null); + } + } + + if (description != null) { + columnDefinition.put("colComment", description); + } + } + + /** + * Processes each model's property mapped to date type and adds related vendor extensions + * + * @param model model + * @param property model's property + */ + public void processDateTypeProperty(CodegenModel model, CodegenProperty property) { + Map vendorExtensions = property.getVendorExtensions(); + Map mysqlSchema = new HashMap(); + Map columnDefinition = new HashMap(); + String baseName = property.getBaseName(); + String dataType = property.getDataType(); + Boolean required = property.getRequired(); + String description = property.getDescription(); + String defaultValue = property.getDefaultValue(); + + if (vendorExtensions.containsKey(CODEGEN_VENDOR_EXTENSION_KEY)) { + // user already specified schema values + LOGGER.info("Found vendor extension in '" + baseName + "' property, autogeneration skipped"); + return; + } + + vendorExtensions.put(CODEGEN_VENDOR_EXTENSION_KEY, mysqlSchema); + mysqlSchema.put("columnDefinition", columnDefinition); + columnDefinition.put("colName", toColumnName(baseName)); + columnDefinition.put("colDataType", dataType); + + if (Boolean.TRUE.equals(required)) { + columnDefinition.put("colNotNull", true); + } else { + columnDefinition.put("colNotNull", false); + try { + columnDefinition.put("colDefault", toCodegenMysqlDataTypeDefault(defaultValue, (String) columnDefinition.get("colDataType"))); + } catch (RuntimeException exception) { + LOGGER.warn("Property '" + baseName + "' of model '" + model.getName() + "' mapped to MySQL data type which doesn't support default value"); + columnDefinition.put("colDefault", null); + } + } + + if (description != null) { + columnDefinition.put("colComment", description); + } + } + + /** + * Processes each model's property mapped to JSON type and adds related vendor extensions + * + * @param model model + * @param property model's property + */ + public void processJsonTypeProperty(CodegenModel model, CodegenProperty property) { + Map vendorExtensions = property.getVendorExtensions(); + Map mysqlSchema = new HashMap(); + Map columnDefinition = new HashMap(); + String baseName = property.getBaseName(); + String dataType = property.getDataType(); + Boolean required = property.getRequired(); + String description = property.getDescription(); + String defaultValue = property.getDefaultValue(); + + if (vendorExtensions.containsKey(CODEGEN_VENDOR_EXTENSION_KEY)) { + // user already specified schema values + LOGGER.info("Found vendor extension in '" + baseName + "' property, autogeneration skipped"); + return; + } + + vendorExtensions.put(CODEGEN_VENDOR_EXTENSION_KEY, mysqlSchema); + mysqlSchema.put("columnDefinition", columnDefinition); + columnDefinition.put("colName", toColumnName(baseName)); + columnDefinition.put("colDataType", dataType); + if (Boolean.FALSE.equals(getJsonDataTypeEnabled())) { + columnDefinition.put("colDataType", "TEXT"); + } + + if (Boolean.TRUE.equals(required)) { + columnDefinition.put("colNotNull", true); + } else { + columnDefinition.put("colNotNull", false); + try { + columnDefinition.put("colDefault", toCodegenMysqlDataTypeDefault(defaultValue, (String) columnDefinition.get("colDataType"))); + } catch (RuntimeException exception) { + LOGGER.warn("Property '" + baseName + "' of model '" + model.getName() + "' mapped to MySQL data type which doesn't support default value"); + columnDefinition.put("colDefault", null); + } + } + + if (description != null) { + columnDefinition.put("colComment", description); + } + } + + /** + * Processes each model's property not mapped to any type and adds related vendor extensions + * Most of time it's related to referenced properties eg. \Model\User + * + * @param model model + * @param property model's property + */ + public void processUnknownTypeProperty(CodegenModel model, CodegenProperty property) { + Map vendorExtensions = property.getVendorExtensions(); + Map mysqlSchema = new HashMap(); + Map columnDefinition = new HashMap(); + String baseName = property.getBaseName(); + Boolean required = property.getRequired(); + String description = property.getDescription(); + String defaultValue = property.getDefaultValue(); + + if (vendorExtensions.containsKey(CODEGEN_VENDOR_EXTENSION_KEY)) { + // user already specified schema values + LOGGER.info("Found vendor extension in '" + baseName + "' property, autogeneration skipped"); + return; + } + + vendorExtensions.put(CODEGEN_VENDOR_EXTENSION_KEY, mysqlSchema); + mysqlSchema.put("columnDefinition", columnDefinition); + columnDefinition.put("colName", toColumnName(baseName)); + columnDefinition.put("colDataType", "TEXT"); + + if (Boolean.TRUE.equals(required)) { + columnDefinition.put("colNotNull", true); + } else { + columnDefinition.put("colNotNull", false); + try { + columnDefinition.put("colDefault", toCodegenMysqlDataTypeDefault(defaultValue, (String) columnDefinition.get("colDataType"))); + } catch (RuntimeException exception) { + LOGGER.warn("Property '" + baseName + "' of model '" + model.getName() + "' mapped to MySQL data type which doesn't support default value"); + columnDefinition.put("colDefault", null); + } + } + + if (description != null) { + columnDefinition.put("colComment", description); + } + } + + /** + * Generates codegen property for MySQL data type argument + * + * @param value argument value + * @param hasMore shows whether codegen has more arguments or not + * @return generated codegen property + */ + public HashMap toCodegenMysqlDataTypeArgument(Object value, Boolean hasMore) { + HashMap arg = new HashMap(); + if (value instanceof String) { + arg.put("isString", true); + arg.put("isFloat", false); + arg.put("isInteger", false); + arg.put("isNumeric", false); + } else if (value instanceof Integer || value instanceof Long) { + arg.put("isString", false); + arg.put("isFloat", false); + arg.put("isInteger", true); + arg.put("isNumeric", true); + } else if (value instanceof Number) { + arg.put("isString", false); + arg.put("isFloat", true); + arg.put("isInteger", false); + arg.put("isNumeric", true); + } else { + LOGGER.warn("MySQL data type argument can be primitive type only. Class '" + value.getClass() + "' is provided"); + } + arg.put("argumentValue", value); + arg.put("hasMore", hasMore); + return arg; + } + + /** + * Generates default codegen property for MySQL column definition + * Ref: https://dev.mysql.com/doc/refman/5.7/en/data-type-defaults.html + * + * @param defaultValue value + * @param mysqlDataType MySQL data type + * @return generated codegen property + */ + public HashMap toCodegenMysqlDataTypeDefault(String defaultValue, String mysqlDataType) { + HashMap defaultMap = new HashMap(); + if (defaultValue == null || defaultValue.toUpperCase(Locale.ROOT).equals("NULL")) { + defaultMap.put("defaultValue", "NULL"); + defaultMap.put("isString", false); + defaultMap.put("isNumeric", false); + defaultMap.put("isKeyword", true); + return defaultMap; + } + + switch (mysqlDataType.toUpperCase(Locale.ROOT)) { + case "TINYINT": + case "SMALLINT": + case "MEDIUMINT": + case "INT": + case "BIGINT": + // SERIAL DEFAULT VALUE is a special case. In the definition of an integer column, it is an alias for NOT NULL AUTO_INCREMENT UNIQUE + if (defaultValue.equals("SERIAL DEFAULT VALUE")) { + defaultMap.put("defaultValue", defaultValue); + defaultMap.put("isString", false); + defaultMap.put("isNumeric", false); + defaultMap.put("isKeyword", true); + } else { + defaultMap.put("defaultValue", defaultValue); + defaultMap.put("isString", false); + defaultMap.put("isNumeric", true); + defaultMap.put("isKeyword", false); + } + return defaultMap; + case "TIMESTAMP": + case "DATETIME": + // The exception is that, for TIMESTAMP and DATETIME columns, you can specify CURRENT_TIMESTAMP as the default + if (defaultValue.equals("CURRENT_TIMESTAMP")) { + defaultMap.put("defaultValue", defaultValue); + defaultMap.put("isString", false); + defaultMap.put("isNumeric", false); + defaultMap.put("isKeyword", true); + } else { + defaultMap.put("defaultValue", defaultValue); + defaultMap.put("isString", true); + defaultMap.put("isNumeric", false); + defaultMap.put("isKeyword", false); + } + return defaultMap; + case "TINYBLOB": + case "BLOB": + case "MEDIUMBLOB": + case "LONGBLOB": + case "TINYTEXT": + case "TEXT": + case "MEDIUMTEXT": + case "LONGTEXT": + case "GEOMETRY": + case "JSON": + // The BLOB, TEXT, GEOMETRY, and JSON data types cannot be assigned a default value. + throw new RuntimeException("The BLOB, TEXT, GEOMETRY, and JSON data types cannot be assigned a default value"); + default: + defaultMap.put("defaultValue", defaultValue); + defaultMap.put("isString", true); + defaultMap.put("isNumeric", false); + defaultMap.put("isKeyword", false); + return defaultMap; + } + } + + /** + * Finds best fitted MySQL data type for integer variable based on minimum and maximum properties + * + * @param minimum (optional) codegen property + * @param maximum (optional) codegen property + * @param unsigned (optional) whether variable is unsigned or not + * @return MySQL integer data type + */ + public String getMysqlMatchedIntegerDataType(Long minimum, Long maximum, Boolean unsigned) { + // we can choose fit mysql data type + // ref: https://dev.mysql.com/doc/refman/8.0/en/integer-types.html + Long min = (minimum != null) ? minimum : -2147483648L; + Long max = (maximum != null) ? maximum : 2147483647L; + Long actualMin = Math.min(min, max); // sometimes min and max values can be mixed up + Long actualMax = Math.max(min, max); // sometimes only minimum specified and it can be pretty high + if (minimum != null && maximum != null && minimum > maximum) { + LOGGER.warn("Codegen property 'minimum' cannot be greater than 'maximum'"); + } + if (Boolean.TRUE.equals(unsigned) && actualMin >= 0) { + if (actualMax <= 255) { + return "TINYINT"; + } else if (actualMax <= 65535) { + return "SMALLINT"; + } else if (actualMax <= 16777215) { + return "MEDIUMINT"; + } else if (actualMax <= 4294967295L) { + return "INT"; + } else if (actualMax > 4294967295L) { + return "BIGINT"; + } + } else { + if (actualMin >= -128 && actualMax <= 127) { + return "TINYINT"; + } else if (actualMin >= -32768 && actualMax <= 32767) { + return "SMALLINT"; + } else if (actualMin >= -8388608 && actualMax <= 8388607) { + return "MEDIUMINT"; + } else if (actualMin >= -2147483648 && actualMax <= 2147483647) { + return "INT"; + } else if (actualMin < -2147483648 || actualMax > 2147483647) { + return "BIGINT"; + } + } + + return "INT"; + } + + /** + * Finds best fitted MySQL data type for string variable based on minLength and maxLength properties + * + * @param minLength (optional) codegen property + * @param maxLength (optional) codegen property + * @return MySQL string data type + */ + public String getMysqlMatchedStringDataType(Integer minLength, Integer maxLength) { + // we can choose fit mysql data type + // ref: https://dev.mysql.com/doc/refman/8.0/en/string-type-overview.html + Integer min = (minLength != null && minLength >= 0) ? minLength : 0; + Integer max = (maxLength != null && maxLength >= 0) ? maxLength : 65535; + Integer actualMin = Math.min(min, max); // sometimes minLength and maxLength values can be mixed up + Integer actualMax = Math.max(min, max); // sometimes only minLength specified and it can be pretty high + if (minLength != null && maxLength != null && minLength > maxLength) { + LOGGER.warn("Codegen property 'minLength' cannot be greater than 'maxLength'"); + } + if (actualMax.equals(actualMin) && actualMax <= 255 ) { + return "CHAR"; + } else if (actualMax <= 255) { + return "VARCHAR"; + } else if (actualMax > 255 && actualMax <= 65535) { + return "TEXT"; + } else if (actualMax > 65535 && actualMax <= 16777215) { + return "MEDIUMTEXT"; + } else if (actualMax > 16777215) { + return "LONGTEXT"; + } + return "TEXT"; + } + + /** + * Checks whether string is one of MySQL Data Types + * Ref: https://dev.mysql.com/doc/refman/8.0/en/data-type-overview.html + * + * @param dataType which needs to check + * @return true if value is correct MySQL data type, otherwise false + */ + public Boolean isMysqlDataType(String dataType) { + return ( + mysqlNumericTypes.contains(dataType.toUpperCase(Locale.ROOT)) || + mysqlDateAndTimeTypes.contains(dataType.toUpperCase(Locale.ROOT)) || + mysqlStringTypes.contains(dataType.toUpperCase(Locale.ROOT)) || + mysqlSpatialTypes.contains(dataType.toUpperCase(Locale.ROOT)) || + dataType.toUpperCase(Locale.ROOT).equals("JSON") + ); + } + + /** + * Converts name to valid MySQL database name + * Produced name must be used with backticks only, eg. `database_name` + * + * @param name source name + * @return database name + */ + public String toDatabaseName(String name) { + String identifier = toMysqlIdentifier(name, databaseNamePrefix, databaseNameSuffix); + if (identifier.length() > IDENTIFIER_MAX_LENGTH) { + LOGGER.warn("Database name cannot exceed 64 chars. Name '" + name + "' will be truncated"); + identifier = identifier.substring(0, IDENTIFIER_MAX_LENGTH); + } + return identifier; + } + + /** + * Converts name to valid MySQL column name + * Produced name must be used with backticks only, eg. `table_name` + * + * @param name source name + * @return table name + */ + public String toTableName(String name) { + String identifier = toMysqlIdentifier(name, tableNamePrefix, tableNameSuffix); + if (identifier.length() > IDENTIFIER_MAX_LENGTH) { + LOGGER.warn("Table name cannot exceed 64 chars. Name '" + name + "' will be truncated"); + identifier = identifier.substring(0, IDENTIFIER_MAX_LENGTH); + } + return identifier; + } + + /** + * Converts name to valid MySQL column name + * Produced name must be used with backticks only, eg. `column_name` + * + * @param name source name + * @return column name + */ + public String toColumnName(String name) { + String identifier = toMysqlIdentifier(name, columnNamePrefix, columnNameSuffix); + if (identifier.length() > IDENTIFIER_MAX_LENGTH) { + LOGGER.warn("Column name cannot exceed 64 chars. Name '" + name + "' will be truncated"); + identifier = identifier.substring(0, IDENTIFIER_MAX_LENGTH); + } + return identifier; + } + + /** + * Converts name to valid MySQL identifier which can be used as database, table, column name + * Produced name must be used with backticks only, eg. `column_name` + * + * @param name source name + * @param prefix when escaped name is digits only, prefix will be prepended + * @param suffix when escaped name is digits only, suffix will be appended + * @return identifier name + */ + public String toMysqlIdentifier(String name, String prefix, String suffix) { + String escapedName = escapeMysqlQuotedIdentifier(name); + // Database, table, and column names cannot end with space characters. + if (escapedName.matches(".*\\s$")) { + LOGGER.warn("Database, table, and column names cannot end with space characters. Check '" + name + "' name" ); + escapedName = escapedName.replaceAll("\\s+$", ""); + } + + // Identifiers may begin with a digit but unless quoted may not consist solely of digits. + if (escapedName.matches("^\\d+$")) { + LOGGER.warn("Database, table, and column names cannot consist solely of digits. Check '" + name + "' name"); + escapedName = prefix + escapedName + suffix; + } + + // identifier name cannot be empty + if (escapedName.isEmpty()) { + throw new RuntimeException("Empty database/table/column name for property '" + name.toString() + "' not allowed"); + } + return escapedName; + } + + /** + * Escapes MySQL identifier to use it in SQL statements without backticks, eg. SELECT identifier FROM + * Ref: https://dev.mysql.com/doc/refman/8.0/en/identifiers.html + * + * @param identifier source identifier + * @return escaped identifier + */ + public String escapeMysqlUnquotedIdentifier(String identifier) { + // ASCII: [0-9,a-z,A-Z$_] (basic Latin letters, digits 0-9, dollar, underscore) Extended: U+0080 .. U+FFFF + Pattern regexp = Pattern.compile("[^0-9a-zA-z$_\\u0080-\\uFFFF]"); + Matcher matcher = regexp.matcher(identifier); + if (matcher.find()) { + LOGGER.warn("Identifier '" + identifier + "' contains unsafe characters out of [0-9,a-z,A-Z$_] and U+0080..U+FFFF range"); + identifier = identifier.replaceAll("[^0-9a-zA-z$_\\u0080-\\uFFFF]", ""); + } + + // ASCII NUL (U+0000) and supplementary characters (U+10000 and higher) are not permitted in quoted or unquoted identifiers. + // Don't know how to match these characters, hope that first regexp already strip them + // Pattern regexp2 = Pattern.compile("[\0\uD800\uDC00-\uDBFF\uDFFF]"); + return identifier; + } + + /** + * Escapes MySQL identifier to use it in SQL statements with backticks, eg. SELECT `identifier` FROM + * Ref: https://dev.mysql.com/doc/refman/8.0/en/identifiers.html + * + * @param identifier source identifier + * @return escaped identifier + */ + public String escapeMysqlQuotedIdentifier(String identifier) { + // ASCII: U+0001 .. U+007F Extended: U+0080 .. U+FFFF + Pattern regexp = Pattern.compile("[^\\u0001-\\u007F\\u0080-\\uFFFF]"); + Matcher matcher = regexp.matcher(identifier); + if (matcher.find()) { + LOGGER.warn("Identifier '" + identifier + "' contains unsafe characters out of U+0001..U+007F and U+0080..U+FFFF range"); + identifier = identifier.replaceAll("[^\\u0001-\\u007F\\u0080-\\uFFFF]", ""); + } + + // ASCII NUL (U+0000) and supplementary characters (U+10000 and higher) are not permitted in quoted or unquoted identifiers. + // Don't know how to match these characters, hope that first regexp already strip them + // Pattern regexp2 = Pattern.compile("[\0\uD800\uDC00-\uDBFF\uDFFF]"); + return identifier; + } + + @Override + public String escapeReservedWord(String name) { + LOGGER.warn("'" + name + "' is MySQL reserved word. Do not use that word or properly escape it with backticks in mustache template"); + return name; + } + + @Override + public String escapeQuotationMark(String input) { + // remove ' to avoid code injection + return input.replace("'", ""); + } + + @Override + public String escapeUnsafeCharacters(String input) { + return input.replace("*/", "*_/").replace("/*", "/_*"); + } + + /** + * Sets default database name for all MySQL queries + * Provided value will be escaped when necessary + * + * @param databaseName source name + */ + public void setDefaultDatabaseName(String databaseName) { + String escapedName = toDatabaseName(databaseName); + if (escapedName.equals(databaseName) == false) { + LOGGER.error("Invalid database name. '" + databaseName + "' cannot be used as MySQL identifier. Escaped value '" + escapedName + "' will be used instead."); + } + this.defaultDatabaseName = escapedName; + } + + /** + * Returns default database name for all MySQL queries + * This value must be used with backticks only, eg. `database_name` + * + * @return default database name + */ + public String getDefaultDatabaseName() { + return this.defaultDatabaseName; + } + + /** + * Enables special JSON data type in all MySQL queries + * JSON data type requires MySQL version 5.7.8 + * + * @param enabled true to enable, otherwise false + */ + public void setJsonDataTypeEnabled(Boolean enabled) { + this.jsonDataTypeEnabled = enabled; + } + + /** + * Whether JSON data type enabled or disabled in all MySQL queries + * + * @return true if enabled otherwise false + */ + public Boolean getJsonDataTypeEnabled() { + return this.jsonDataTypeEnabled; + } + +} diff --git a/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig index cdc9e925cdb..1659fc4169c 100644 --- a/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig +++ b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig @@ -53,6 +53,7 @@ org.openapitools.codegen.languages.JavascriptFlowtypedClientCodegen org.openapitools.codegen.languages.JavascriptClosureAngularClientCodegen org.openapitools.codegen.languages.JMeterClientCodegen org.openapitools.codegen.languages.LuaClientCodegen +org.openapitools.codegen.languages.MysqlSchemaCodegen org.openapitools.codegen.languages.NodeJSServerCodegen org.openapitools.codegen.languages.ObjcClientCodegen org.openapitools.codegen.languages.OpenAPIGenerator diff --git a/modules/openapi-generator/src/main/resources/mysql-schema/README.mustache b/modules/openapi-generator/src/main/resources/mysql-schema/README.mustache new file mode 100644 index 00000000000..54bda8398ac --- /dev/null +++ b/modules/openapi-generator/src/main/resources/mysql-schema/README.mustache @@ -0,0 +1,48 @@ +# MySQL Schema Codegen + +Main goal of this generator is to provide database structure file almost identical you usually generate with: +- PHPMyAdmin (Export structure only, SQL syntax) +- Adminer +- `mysqldump` function + +[MySQL documentation](https://dev.mysql.com/doc/) + +## Requirements +- MySQL Server ^5.7.8 (`JSON` column type added) + +## Openapi Data Type to MySQL Data Type mapping + +| Openapi Data Type | Openapi Data Format | Dependent properties | MySQL Data Types | Default MySQL Data Type | +| --- | --- | --- | --- | --- | +| `integer` | `int32` | `minimum` / `maximum` / `minimumExclusive` / `maximumExclusive` | `TINYINT` / `SMALLINT` / `MEDIUMINT`/ `INT` / `BIGINT` | `INT` | +| `integer` | `int64` | `minimum` / `maximum` / `minimumExclusive` / `maximumExclusive` | `TINYINT` / `SMALLINT` / `MEDIUMINT` / `INT` / `BIGINT` | `BIGINT` | +| `boolean` | | | `TINYINT` | `TINYINT` | +| `number` | `float` | | `DECIMAL` | `DECIMAL` | +| `number` | `double` | | `DECIMAL` | `DECIMAL` | +| `string` | | `minLength` / `maxLength` | `CHAR` / `VARCHAR` / `TEXT` / `MEDIUMTEXT` / `LONGTEXT` | `TEXT` | +| `string` | `byte` | | `TEXT` | `TEXT` | +| `string` | `binary` | | `MEDIUMBLOB` | `MEDIUMBLOB` | +| `file` | | | `MEDIUMBLOB` | `MEDIUMBLOB` | +| `string` | `date` | | `DATE` | `DATE` | +| `string` | `date-time` | | `DATETIME` | `DATETIME` | +| `string` | `enum` | | `ENUM` | `ENUM` | +| `array` | | | `JSON` | `JSON` | +| `object` | | | `JSON` | `JSON` | +| `\Model\User` (referenced definition) | | | `TEXT` | `TEXT` | + +## How to use + +Produced file(`mysql_schema.sql`) contains every table definition. Current implementation doesn't drop or modify existed tables, if you want rewrite whole schema make sure they're not presented. + +### PHPMyAdmin + +1. Choose **Import** tab from the home screen +2. In section **File to import** click to **Choose File** and find generated `mysql_schema.sql` +3. Make sure **Format** selector set to **SQL** +4. Push **Go** button + +### Adminer + +1. Click **Import** link in left sidebar +2. In **File upload** fieldset click to **Choose Files** and find generated `mysql_schema.sql` +3. Push **Execute** button diff --git a/modules/openapi-generator/src/main/resources/mysql-schema/mysql_schema.mustache b/modules/openapi-generator/src/main/resources/mysql-schema/mysql_schema.mustache new file mode 100644 index 00000000000..180e2c853f1 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/mysql-schema/mysql_schema.mustache @@ -0,0 +1,45 @@ +/* SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; */ +/* SET AUTOCOMMIT = 0; */ +/* START TRANSACTION; */ +/* SET time_zone = "+00:00"; */ +{{#defaultDatabaseName}} +-- +-- Database: `{{{defaultDatabaseName}}}` +-- +CREATE DATABASE IF NOT EXISTS `{{{defaultDatabaseName}}}` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +{{/defaultDatabaseName}} + +-- -------------------------------------------------------- + +{{#models}}{{#model}}{{#hasVars}}{{^isArrayModel}}-- +-- Table structure{{#vendorExtensions}}{{#x-mysqlSchema}}{{#tableDefinition}} for table `{{tblName}}`{{/tableDefinition}}{{/x-mysqlSchema}}{{/vendorExtensions}} generated from model '{{classVarName}}' +{{#description}} +-- {{description}} +{{/description}} +-- + +{{#vendorExtensions}} +{{#x-mysqlSchema}} +{{#tableDefinition}} +CREATE TABLE IF NOT EXISTS {{#defaultDatabaseName}}`{{{defaultDatabaseName}}}`.{{/defaultDatabaseName}}`{{tblName}}` ( +{{/tableDefinition}} +{{/x-mysqlSchema}} +{{/vendorExtensions}} + {{#vars}} + {{#vendorExtensions}} + {{#x-mysqlSchema}} + {{#columnDefinition}} + `{{colName}}` {{colDataType}}{{#colDataTypeArguments}}{{#-first}}({{/-first}}{{#isString}}'{{/isString}}{{argumentValue}}{{#isString}}'{{/isString}}{{^-last}}, {{/-last}}{{#-last}}){{/-last}}{{/colDataTypeArguments}}{{#colUnsigned}} UNSIGNED{{/colUnsigned}}{{#colNotNull}} NOT NULL{{/colNotNull}}{{#colDefault}} DEFAULT {{#isString}}'{{defaultValue}}'{{/isString}}{{^isString}}{{defaultValue}}{{/isString}}{{/colDefault}}{{#colComment}} COMMENT '{{colComment}}'{{/colComment}}{{^-last}},{{/-last}} + {{/columnDefinition}} + {{/x-mysqlSchema}} + {{/vendorExtensions}} + {{/vars}} +{{#vendorExtensions}} +{{#x-mysqlSchema}} +{{#tableDefinition}} +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci{{#tblComment}} COMMENT='{{tblComment}}'{{/tblComment}}; +{{/tableDefinition}} +{{/x-mysqlSchema}} +{{/vendorExtensions}} + +{{/isArrayModel}}{{/hasVars}}{{/model}}{{/models}} \ No newline at end of file diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/mysql/MysqlSchemaCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/mysql/MysqlSchemaCodegenTest.java new file mode 100644 index 00000000000..1d9153933cb --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/mysql/MysqlSchemaCodegenTest.java @@ -0,0 +1,278 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openapitools.codegen.mysql; + +import org.testng.Assert; +import org.testng.annotations.Test; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; +import java.util.ArrayList; +import java.util.Arrays; + +import org.openapitools.codegen.languages.MysqlSchemaCodegen; + +public class MysqlSchemaCodegenTest { + + @Test + public void testGetMysqlMatchedIntegerDataType() { + final MysqlSchemaCodegen codegen = new MysqlSchemaCodegen(); + Assert.assertSame(codegen.getMysqlMatchedIntegerDataType(null, null, null), "INT"); + + Assert.assertSame(codegen.getMysqlMatchedIntegerDataType(-128L, 127L, false), "TINYINT"); + Assert.assertSame(codegen.getMysqlMatchedIntegerDataType(0L, 255L, true), "TINYINT"); + + Assert.assertSame(codegen.getMysqlMatchedIntegerDataType(500L, 100L, null), "SMALLINT"); + Assert.assertSame(codegen.getMysqlMatchedIntegerDataType(500L, 100L, true), "SMALLINT"); + Assert.assertSame(codegen.getMysqlMatchedIntegerDataType(500L, 100L, false), "SMALLINT"); + Assert.assertSame(codegen.getMysqlMatchedIntegerDataType(-32768L, 32767L, false), "SMALLINT"); + Assert.assertSame(codegen.getMysqlMatchedIntegerDataType(0L, 65535L, true), "SMALLINT"); + + Assert.assertSame(codegen.getMysqlMatchedIntegerDataType(-8388608L, 8388607L, false), "MEDIUMINT"); + Assert.assertSame(codegen.getMysqlMatchedIntegerDataType(0L, 16777215L, true), "MEDIUMINT"); + + Assert.assertSame(codegen.getMysqlMatchedIntegerDataType(-2147483648L, 2147483647L, false), "INT"); + Assert.assertSame(codegen.getMysqlMatchedIntegerDataType(Long.parseLong(String.valueOf(Integer.MIN_VALUE)), Long.parseLong(String.valueOf(Integer.MAX_VALUE)), false), "INT"); + Assert.assertSame(codegen.getMysqlMatchedIntegerDataType(0L, 4294967295L, true), "INT"); + + Assert.assertSame(codegen.getMysqlMatchedIntegerDataType(-2147483649L, 2147483648L, false), "BIGINT"); + Assert.assertSame(codegen.getMysqlMatchedIntegerDataType(0L, 4294967296L, true), "BIGINT"); + } + + @Test + public void testGetMysqlMatchedStringDataType() { + final MysqlSchemaCodegen codegen = new MysqlSchemaCodegen(); + Assert.assertSame(codegen.getMysqlMatchedStringDataType(6, 6), "CHAR"); + Assert.assertSame(codegen.getMysqlMatchedStringDataType(0, 0), "CHAR"); + Assert.assertSame(codegen.getMysqlMatchedStringDataType(255, 255), "CHAR"); + + Assert.assertSame(codegen.getMysqlMatchedStringDataType(null, 100), "VARCHAR"); + Assert.assertSame(codegen.getMysqlMatchedStringDataType(null, 255), "VARCHAR"); + Assert.assertSame(codegen.getMysqlMatchedStringDataType(50, 255), "VARCHAR"); + Assert.assertSame(codegen.getMysqlMatchedStringDataType(100, 20), "VARCHAR"); + + Assert.assertSame(codegen.getMysqlMatchedStringDataType(null, null), "TEXT"); + Assert.assertSame(codegen.getMysqlMatchedStringDataType(100, null), "TEXT"); + Assert.assertSame(codegen.getMysqlMatchedStringDataType(255, null), "TEXT"); + Assert.assertSame(codegen.getMysqlMatchedStringDataType(null, 256), "TEXT"); + + Assert.assertSame(codegen.getMysqlMatchedStringDataType(16777215, null), "MEDIUMTEXT"); + Assert.assertSame(codegen.getMysqlMatchedStringDataType(16777215, 100), "MEDIUMTEXT"); + Assert.assertSame(codegen.getMysqlMatchedStringDataType(null, 16777215), "MEDIUMTEXT"); + Assert.assertSame(codegen.getMysqlMatchedStringDataType(100, 16777215), "MEDIUMTEXT"); + + Assert.assertSame(codegen.getMysqlMatchedStringDataType(16777216, null), "LONGTEXT"); + Assert.assertSame(codegen.getMysqlMatchedStringDataType(null, 16777216), "LONGTEXT"); + Assert.assertSame(codegen.getMysqlMatchedStringDataType(16777216, 16777216), "LONGTEXT"); + Assert.assertSame(codegen.getMysqlMatchedStringDataType(100, 16777216), "LONGTEXT"); + Assert.assertSame(codegen.getMysqlMatchedStringDataType(100, Integer.MAX_VALUE), "LONGTEXT"); + } + + @Test + public void testToCodegenMysqlDataTypeArgument() { + final MysqlSchemaCodegen codegen = new MysqlSchemaCodegen(); + String strArgument = "HelloWorld"; + HashMap strProp = codegen.toCodegenMysqlDataTypeArgument(strArgument, true); + Assert.assertTrue((Boolean) strProp.get("isString")); + Assert.assertTrue((Boolean) strProp.get("hasMore")); + Assert.assertFalse((Boolean) strProp.get("isFloat")); + Assert.assertFalse((Boolean) strProp.get("isInteger")); + Assert.assertFalse((Boolean) strProp.get("isNumeric")); + Assert.assertSame((String) strProp.get("argumentValue"), strArgument); + + Integer intArgument = 10; + HashMap intProp = codegen.toCodegenMysqlDataTypeArgument(intArgument, true); + Assert.assertFalse((Boolean) intProp.get("isString")); + Assert.assertTrue((Boolean) intProp.get("hasMore")); + Assert.assertFalse((Boolean) intProp.get("isFloat")); + Assert.assertTrue((Boolean) intProp.get("isInteger")); + Assert.assertTrue((Boolean) intProp.get("isNumeric")); + Assert.assertSame((Integer) intProp.get("argumentValue"), intArgument); + + Double floatArgument = 3.14; + HashMap floatProp = codegen.toCodegenMysqlDataTypeArgument(floatArgument, false); + Assert.assertFalse((Boolean) floatProp.get("isString")); + Assert.assertFalse((Boolean) floatProp.get("hasMore")); + Assert.assertTrue((Boolean) floatProp.get("isFloat")); + Assert.assertFalse((Boolean) floatProp.get("isInteger")); + Assert.assertTrue((Boolean) floatProp.get("isNumeric")); + Assert.assertSame((Double) floatProp.get("argumentValue"), floatArgument); + } + + @Test + public void testToCodegenMysqlDataTypeDefault() { + final MysqlSchemaCodegen codegen = new MysqlSchemaCodegen(); + HashMap defaultMap = null; + ArrayList intFixture = new ArrayList(Arrays.asList( + "TINYINT", "SmallInt", "Mediumint", "INT", "bigint" + )); + for(String intType : intFixture) { + defaultMap = codegen.toCodegenMysqlDataTypeDefault("150", intType); + Assert.assertTrue((Boolean) defaultMap.get("isNumeric")); + Assert.assertFalse((Boolean) defaultMap.get("isString")); + Assert.assertFalse((Boolean) defaultMap.get("isKeyword")); + Assert.assertSame(defaultMap.get("defaultValue"), "150"); + } + defaultMap = codegen.toCodegenMysqlDataTypeDefault("SERIAL DEFAULT VALUE", "TINYINT"); + Assert.assertFalse((Boolean) defaultMap.get("isNumeric")); + Assert.assertFalse((Boolean) defaultMap.get("isString")); + Assert.assertTrue((Boolean) defaultMap.get("isKeyword")); + Assert.assertSame(defaultMap.get("defaultValue"), "SERIAL DEFAULT VALUE"); + + ArrayList dateFixture = new ArrayList(Arrays.asList( + "Timestamp", "DateTime" + )); + for(String dateType : dateFixture) { + defaultMap = codegen.toCodegenMysqlDataTypeDefault("2018-08-12", dateType); + Assert.assertFalse((Boolean) defaultMap.get("isNumeric")); + Assert.assertTrue((Boolean) defaultMap.get("isString")); + Assert.assertFalse((Boolean) defaultMap.get("isKeyword")); + Assert.assertSame(defaultMap.get("defaultValue"), "2018-08-12"); + } + defaultMap = codegen.toCodegenMysqlDataTypeDefault("CURRENT_TIMESTAMP", "Timestamp"); + Assert.assertFalse((Boolean) defaultMap.get("isNumeric")); + Assert.assertFalse((Boolean) defaultMap.get("isString")); + Assert.assertTrue((Boolean) defaultMap.get("isKeyword")); + Assert.assertSame(defaultMap.get("defaultValue"), "CURRENT_TIMESTAMP"); + + ArrayList restFixture = new ArrayList(Arrays.asList( + "VARCHAR", "CHAR", "ENUM", "UNKNOWN" + )); + for(String restType : restFixture) { + defaultMap = codegen.toCodegenMysqlDataTypeDefault("sometext", restType); + Assert.assertFalse((Boolean) defaultMap.get("isNumeric")); + Assert.assertTrue((Boolean) defaultMap.get("isString")); + Assert.assertFalse((Boolean) defaultMap.get("isKeyword")); + Assert.assertSame(defaultMap.get("defaultValue"), "sometext"); + } + } + + @Test(expectedExceptions = RuntimeException.class) + public void testToCodegenMysqlDataTypeDefaultWithExceptionalColumnType() { + final MysqlSchemaCodegen codegen = new MysqlSchemaCodegen(); + HashMap defaultMap = null; + ArrayList specialFixture = new ArrayList(Arrays.asList( + "TINYBLOB", "Blob", "MEDIUMBLOB", "LONGBLOB", "TINYTEXT", "TEXT", "MEDIUMTEXT", "LONGTEXT", "GEOMETRY", "JSON" + )); + for(String specialType : specialFixture) { + defaultMap = codegen.toCodegenMysqlDataTypeDefault("2018-08-12", specialType); + Assert.assertNull(defaultMap); + } + } + + @Test + public void testIsMysqlDataType() { + final MysqlSchemaCodegen codegen = new MysqlSchemaCodegen(); + ArrayList trueFixture = new ArrayList(Arrays.asList( + "INTEGER", "integer", "Integer", "DATETIME", "datetime", "DateTime", "VARCHAR", "varchar", "VarChar", "POINT", "Point", "point", "JSON", "json", "Json" + )); + ArrayList falseFixture = new ArrayList(Arrays.asList( + "unknown", "HashMap", "HASHMAP", "hashmap" + )); + for(String trueValue : trueFixture) { + Assert.assertTrue(codegen.isMysqlDataType(trueValue), "'" + trueValue + "' isn't MySQL data type"); + } + for(String falseValue : falseFixture) { + Assert.assertFalse(codegen.isMysqlDataType(falseValue), "'" + falseValue + "' is MySQL data type"); + } + } + + @Test + public void testToMysqlIdentifier() { + final MysqlSchemaCodegen codegen = new MysqlSchemaCodegen(); + Assert.assertEquals(codegen.toMysqlIdentifier("table_name", "tbl_", ""), "table_name"); + Assert.assertEquals(codegen.toMysqlIdentifier("table_name ", "tbl_", ""), "table_name"); + Assert.assertEquals(codegen.toMysqlIdentifier("12345678", "tbl_", ""), "tbl_12345678"); + } + + @Test(expectedExceptions = RuntimeException.class) + public void testToMysqlIdentifierWithEmptyString() { + final MysqlSchemaCodegen codegen = new MysqlSchemaCodegen(); + codegen.toMysqlIdentifier(" ", "tbl_", ""); + } + + @Test + public void testEscapeMysqlUnquotedIdentifier() { + final MysqlSchemaCodegen codegen = new MysqlSchemaCodegen(); + Assert.assertEquals(codegen.escapeMysqlUnquotedIdentifier("table1Z$_"), "table1Z$_"); + Assert.assertEquals(codegen.escapeMysqlUnquotedIdentifier("table1Z$_!#%~&?()*+-./"), "table1Z$_"); + Assert.assertEquals(codegen.escapeMysqlUnquotedIdentifier("table1Z$_русскийтекст"), "table1Z$_русскийтекст"); + Assert.assertEquals(codegen.escapeMysqlQuotedIdentifier("table𐀀"), "table"); + Assert.assertEquals(codegen.escapeMysqlQuotedIdentifier("table_name!'()�"), "table_name!'()�"); + Assert.assertEquals(codegen.escapeMysqlQuotedIdentifier("table_name𐌅𐌌"), "table_name"); + } + + @Test + public void testEscapeMysqlQuotedIdentifier() { + final MysqlSchemaCodegen codegen = new MysqlSchemaCodegen(); + Assert.assertEquals(codegen.escapeMysqlQuotedIdentifier("table"), "table"); + Assert.assertEquals(codegen.escapeMysqlQuotedIdentifier("table𐀀"), "table"); + Assert.assertEquals(codegen.escapeMysqlQuotedIdentifier("table_name!'()�"), "table_name!'()�"); + Assert.assertEquals(codegen.escapeMysqlQuotedIdentifier("table_name𐌅𐌌"), "table_name"); + } + + @Test + public void testIsReservedWord() { + final MysqlSchemaCodegen codegen = new MysqlSchemaCodegen(); + Set reservedWords = codegen.reservedWords(); + ArrayList trueFixture = new ArrayList(Arrays.asList( + "accessible", "asc", "between", "blob", "change", "column", "day_hour", "distinct", "enclosed", "except", "explain", "float", "for", "function", "grant", "grouping", "high_priority", "groups", "hour_minute", "insensitive", "interval", "json_table", "keys", "kill", "leave", "left", "mediumblob", "modifies", "not", "null", "numeric", "optimize", "outer", "precision", "primary", "references", "replace", "select", "sql", "then", "tinytext", "unique", "unlock", "varchar", "virtual", "when", "where", "xor", "year_month", "zerofill" + )); + ArrayList falseFixture = new ArrayList(Arrays.asList( + "after", "boolean", "charset", "cpu", "current", "delay_key_write", "end", "format", "global", "host", "install", "json", "key_block_size", "local", "max_size", "none", "offset", "partial", "quarter", "relay", "second", "status", "timestamp", "until", "variables", "without", "xml", "year" + )); + for(String trueValue : trueFixture) { + Assert.assertTrue(reservedWords.contains(trueValue), "'" + trueValue + "' isn't MySQL reserved word"); + } + for(String falseValue : falseFixture) { + Assert.assertFalse(reservedWords.contains(falseValue), "'" + falseValue + "' is MySQL reserved word"); + } + } + + @Test + public void testSetDefaultDatabaseName() { + final MysqlSchemaCodegen codegen = new MysqlSchemaCodegen(); + codegen.setDefaultDatabaseName("valid_db_name"); + Assert.assertSame(codegen.getDefaultDatabaseName(), "valid_db_name"); + codegen.setDefaultDatabaseName("12345"); + Assert.assertNotSame(codegen.getDefaultDatabaseName(), "12345"); + } + + @Test + public void testGetDefaultDatabaseName() { + final MysqlSchemaCodegen codegen = new MysqlSchemaCodegen(); + Assert.assertSame(codegen.getDefaultDatabaseName(), ""); + } + + @Test + public void testSetJsonDataTypeEnabled() { + final MysqlSchemaCodegen codegen = new MysqlSchemaCodegen(); + codegen.setJsonDataTypeEnabled(true); + Assert.assertTrue(codegen.getJsonDataTypeEnabled()); + codegen.setJsonDataTypeEnabled(false); + Assert.assertFalse(codegen.getJsonDataTypeEnabled()); + } + + @Test + public void testGetJsonDataTypeEnabled() { + final MysqlSchemaCodegen codegen = new MysqlSchemaCodegen(); + Assert.assertTrue(codegen.getJsonDataTypeEnabled()); + codegen.setJsonDataTypeEnabled(false); + Assert.assertFalse(codegen.getJsonDataTypeEnabled()); + } + +} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/mysql/MysqlSchemaOptionsTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/mysql/MysqlSchemaOptionsTest.java new file mode 100644 index 00000000000..d7afd576d9c --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/mysql/MysqlSchemaOptionsTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openapitools.codegen.mysql; + +import org.openapitools.codegen.AbstractOptionsTest; +import org.openapitools.codegen.CodegenConfig; +import org.openapitools.codegen.languages.MysqlSchemaCodegen; +import org.openapitools.codegen.options.MysqlSchemaOptionsProvider; + +import mockit.Expectations; +import mockit.Tested; + +public class MysqlSchemaOptionsTest extends AbstractOptionsTest { + + @Tested + private MysqlSchemaCodegen clientCodegen; + + public MysqlSchemaOptionsTest() { + super(new MysqlSchemaOptionsProvider()); + } + + @Override + protected CodegenConfig getCodegenConfig() { + return clientCodegen; + } + + @SuppressWarnings("unused") + @Override + protected void setExpectations() { + new Expectations(clientCodegen) {{ + clientCodegen.setDefaultDatabaseName(MysqlSchemaOptionsProvider.DEFAULT_DATABASE_NAME_VALUE); + times = 1; + clientCodegen.setJsonDataTypeEnabled(Boolean.valueOf(MysqlSchemaOptionsProvider.JSON_DATA_TYPE_ENABLED_VALUE)); + times = 1; + }}; + } +} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/options/MysqlSchemaOptionsProvider.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/options/MysqlSchemaOptionsProvider.java new file mode 100644 index 00000000000..e1e51a61d89 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/options/MysqlSchemaOptionsProvider.java @@ -0,0 +1,46 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openapitools.codegen.options; + +import org.openapitools.codegen.languages.MysqlSchemaCodegen; + +import com.google.common.collect.ImmutableMap; + +import java.util.Map; + +public class MysqlSchemaOptionsProvider implements OptionsProvider { + public static final String DEFAULT_DATABASE_NAME_VALUE = "database_name"; + public static final String JSON_DATA_TYPE_ENABLED_VALUE = "false"; + + @Override + public String getLanguage() { + return "mysql-schema"; + } + + @Override + public Map createOptions() { + ImmutableMap.Builder builder = new ImmutableMap.Builder(); + return builder.put(MysqlSchemaCodegen.DEFAULT_DATABASE_NAME, DEFAULT_DATABASE_NAME_VALUE) + .put(MysqlSchemaCodegen.JSON_DATA_TYPE_ENABLED, JSON_DATA_TYPE_ENABLED_VALUE) + .build(); + } + + @Override + public boolean isServer() { + return false; + } +} diff --git a/samples/schema/petstore-security-test/mysql/.openapi-generator-ignore b/samples/schema/petstore-security-test/mysql/.openapi-generator-ignore new file mode 100644 index 00000000000..7484ee590a3 --- /dev/null +++ b/samples/schema/petstore-security-test/mysql/.openapi-generator-ignore @@ -0,0 +1,23 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/samples/schema/petstore-security-test/mysql/.openapi-generator/VERSION b/samples/schema/petstore-security-test/mysql/.openapi-generator/VERSION new file mode 100644 index 00000000000..6d94c9c2e12 --- /dev/null +++ b/samples/schema/petstore-security-test/mysql/.openapi-generator/VERSION @@ -0,0 +1 @@ +3.3.0-SNAPSHOT \ No newline at end of file diff --git a/samples/schema/petstore-security-test/mysql/README.md b/samples/schema/petstore-security-test/mysql/README.md new file mode 100644 index 00000000000..54bda8398ac --- /dev/null +++ b/samples/schema/petstore-security-test/mysql/README.md @@ -0,0 +1,48 @@ +# MySQL Schema Codegen + +Main goal of this generator is to provide database structure file almost identical you usually generate with: +- PHPMyAdmin (Export structure only, SQL syntax) +- Adminer +- `mysqldump` function + +[MySQL documentation](https://dev.mysql.com/doc/) + +## Requirements +- MySQL Server ^5.7.8 (`JSON` column type added) + +## Openapi Data Type to MySQL Data Type mapping + +| Openapi Data Type | Openapi Data Format | Dependent properties | MySQL Data Types | Default MySQL Data Type | +| --- | --- | --- | --- | --- | +| `integer` | `int32` | `minimum` / `maximum` / `minimumExclusive` / `maximumExclusive` | `TINYINT` / `SMALLINT` / `MEDIUMINT`/ `INT` / `BIGINT` | `INT` | +| `integer` | `int64` | `minimum` / `maximum` / `minimumExclusive` / `maximumExclusive` | `TINYINT` / `SMALLINT` / `MEDIUMINT` / `INT` / `BIGINT` | `BIGINT` | +| `boolean` | | | `TINYINT` | `TINYINT` | +| `number` | `float` | | `DECIMAL` | `DECIMAL` | +| `number` | `double` | | `DECIMAL` | `DECIMAL` | +| `string` | | `minLength` / `maxLength` | `CHAR` / `VARCHAR` / `TEXT` / `MEDIUMTEXT` / `LONGTEXT` | `TEXT` | +| `string` | `byte` | | `TEXT` | `TEXT` | +| `string` | `binary` | | `MEDIUMBLOB` | `MEDIUMBLOB` | +| `file` | | | `MEDIUMBLOB` | `MEDIUMBLOB` | +| `string` | `date` | | `DATE` | `DATE` | +| `string` | `date-time` | | `DATETIME` | `DATETIME` | +| `string` | `enum` | | `ENUM` | `ENUM` | +| `array` | | | `JSON` | `JSON` | +| `object` | | | `JSON` | `JSON` | +| `\Model\User` (referenced definition) | | | `TEXT` | `TEXT` | + +## How to use + +Produced file(`mysql_schema.sql`) contains every table definition. Current implementation doesn't drop or modify existed tables, if you want rewrite whole schema make sure they're not presented. + +### PHPMyAdmin + +1. Choose **Import** tab from the home screen +2. In section **File to import** click to **Choose File** and find generated `mysql_schema.sql` +3. Make sure **Format** selector set to **SQL** +4. Push **Go** button + +### Adminer + +1. Click **Import** link in left sidebar +2. In **File upload** fieldset click to **Choose Files** and find generated `mysql_schema.sql` +3. Push **Execute** button diff --git a/samples/schema/petstore-security-test/mysql/mysql_schema.sql b/samples/schema/petstore-security-test/mysql/mysql_schema.sql new file mode 100644 index 00000000000..c913612f55b --- /dev/null +++ b/samples/schema/petstore-security-test/mysql/mysql_schema.sql @@ -0,0 +1,16 @@ +/* SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; */ +/* SET AUTOCOMMIT = 0; */ +/* START TRANSACTION; */ +/* SET time_zone = "+00:00"; */ + +-- -------------------------------------------------------- + +-- +-- Table structure for table `Return` generated from model 'Return' +-- Model for testing reserved words *_/ ' \" =end -- \\r\\n \\n \\r +-- + +CREATE TABLE IF NOT EXISTS `Return` ( + `return` INT DEFAULT NULL COMMENT 'property description *_/ ' \" =end -- \\r\\n \\n \\r' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Model for testing reserved words *_/ ' \" =end -- \\r\\n \\n \\r'; + diff --git a/samples/schema/petstore/mysql/.openapi-generator-ignore b/samples/schema/petstore/mysql/.openapi-generator-ignore new file mode 100644 index 00000000000..7484ee590a3 --- /dev/null +++ b/samples/schema/petstore/mysql/.openapi-generator-ignore @@ -0,0 +1,23 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/samples/schema/petstore/mysql/.openapi-generator/VERSION b/samples/schema/petstore/mysql/.openapi-generator/VERSION new file mode 100644 index 00000000000..6d94c9c2e12 --- /dev/null +++ b/samples/schema/petstore/mysql/.openapi-generator/VERSION @@ -0,0 +1 @@ +3.3.0-SNAPSHOT \ No newline at end of file diff --git a/samples/schema/petstore/mysql/README.md b/samples/schema/petstore/mysql/README.md new file mode 100644 index 00000000000..54bda8398ac --- /dev/null +++ b/samples/schema/petstore/mysql/README.md @@ -0,0 +1,48 @@ +# MySQL Schema Codegen + +Main goal of this generator is to provide database structure file almost identical you usually generate with: +- PHPMyAdmin (Export structure only, SQL syntax) +- Adminer +- `mysqldump` function + +[MySQL documentation](https://dev.mysql.com/doc/) + +## Requirements +- MySQL Server ^5.7.8 (`JSON` column type added) + +## Openapi Data Type to MySQL Data Type mapping + +| Openapi Data Type | Openapi Data Format | Dependent properties | MySQL Data Types | Default MySQL Data Type | +| --- | --- | --- | --- | --- | +| `integer` | `int32` | `minimum` / `maximum` / `minimumExclusive` / `maximumExclusive` | `TINYINT` / `SMALLINT` / `MEDIUMINT`/ `INT` / `BIGINT` | `INT` | +| `integer` | `int64` | `minimum` / `maximum` / `minimumExclusive` / `maximumExclusive` | `TINYINT` / `SMALLINT` / `MEDIUMINT` / `INT` / `BIGINT` | `BIGINT` | +| `boolean` | | | `TINYINT` | `TINYINT` | +| `number` | `float` | | `DECIMAL` | `DECIMAL` | +| `number` | `double` | | `DECIMAL` | `DECIMAL` | +| `string` | | `minLength` / `maxLength` | `CHAR` / `VARCHAR` / `TEXT` / `MEDIUMTEXT` / `LONGTEXT` | `TEXT` | +| `string` | `byte` | | `TEXT` | `TEXT` | +| `string` | `binary` | | `MEDIUMBLOB` | `MEDIUMBLOB` | +| `file` | | | `MEDIUMBLOB` | `MEDIUMBLOB` | +| `string` | `date` | | `DATE` | `DATE` | +| `string` | `date-time` | | `DATETIME` | `DATETIME` | +| `string` | `enum` | | `ENUM` | `ENUM` | +| `array` | | | `JSON` | `JSON` | +| `object` | | | `JSON` | `JSON` | +| `\Model\User` (referenced definition) | | | `TEXT` | `TEXT` | + +## How to use + +Produced file(`mysql_schema.sql`) contains every table definition. Current implementation doesn't drop or modify existed tables, if you want rewrite whole schema make sure they're not presented. + +### PHPMyAdmin + +1. Choose **Import** tab from the home screen +2. In section **File to import** click to **Choose File** and find generated `mysql_schema.sql` +3. Make sure **Format** selector set to **SQL** +4. Push **Go** button + +### Adminer + +1. Click **Import** link in left sidebar +2. In **File upload** fieldset click to **Choose Files** and find generated `mysql_schema.sql` +3. Push **Execute** button diff --git a/samples/schema/petstore/mysql/mysql_schema.sql b/samples/schema/petstore/mysql/mysql_schema.sql new file mode 100644 index 00000000000..808ea185ea5 --- /dev/null +++ b/samples/schema/petstore/mysql/mysql_schema.sql @@ -0,0 +1,333 @@ +/* SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; */ +/* SET AUTOCOMMIT = 0; */ +/* START TRANSACTION; */ +/* SET time_zone = "+00:00"; */ + +-- -------------------------------------------------------- + +-- +-- Table structure for table `$special[model.name]` generated from model 'DollarspecialLeft_Square_BracketmodelPeriodnameRight_Square_Bracket' +-- + +CREATE TABLE IF NOT EXISTS `$special[model.name]` ( + `$special[property.name]` BIGINT DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `200_response` generated from model '200Underscoreresponse' +-- Model for testing model name starting with number +-- + +CREATE TABLE IF NOT EXISTS `200_response` ( + `name` INT DEFAULT NULL, + `class` TEXT DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Model for testing model name starting with number'; + +-- +-- Table structure for table `AdditionalPropertiesClass` generated from model 'AdditionalPropertiesClass' +-- + +CREATE TABLE IF NOT EXISTS `AdditionalPropertiesClass` ( + `map_property` JSON DEFAULT NULL, + `map_of_map_property` JSON DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `Animal` generated from model 'Animal' +-- + +CREATE TABLE IF NOT EXISTS `Animal` ( + `className` TEXT NOT NULL, + `color` TEXT +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `ApiResponse` generated from model 'ApiResponse' +-- + +CREATE TABLE IF NOT EXISTS `ApiResponse` ( + `code` INT DEFAULT NULL, + `type` TEXT DEFAULT NULL, + `message` TEXT DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `ArrayOfArrayOfNumberOnly` generated from model 'ArrayOfArrayOfNumberOnly' +-- + +CREATE TABLE IF NOT EXISTS `ArrayOfArrayOfNumberOnly` ( + `ArrayArrayNumber` JSON DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `ArrayOfNumberOnly` generated from model 'ArrayOfNumberOnly' +-- + +CREATE TABLE IF NOT EXISTS `ArrayOfNumberOnly` ( + `ArrayNumber` JSON DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `ArrayTest` generated from model 'ArrayTest' +-- + +CREATE TABLE IF NOT EXISTS `ArrayTest` ( + `array_of_string` JSON DEFAULT NULL, + `array_array_of_integer` JSON DEFAULT NULL, + `array_array_of_model` JSON DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `Capitalization` generated from model 'Capitalization' +-- + +CREATE TABLE IF NOT EXISTS `Capitalization` ( + `smallCamel` TEXT DEFAULT NULL, + `CapitalCamel` TEXT DEFAULT NULL, + `small_Snake` TEXT DEFAULT NULL, + `Capital_Snake` TEXT DEFAULT NULL, + `SCA_ETH_Flow_Points` TEXT DEFAULT NULL, + `ATT_NAME` TEXT DEFAULT NULL COMMENT 'Name of the pet ' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `Cat` generated from model 'Cat' +-- + +CREATE TABLE IF NOT EXISTS `Cat` ( + `className` TEXT NOT NULL, + `color` TEXT, + `declawed` TINYINT(1) DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `Category` generated from model 'Category' +-- + +CREATE TABLE IF NOT EXISTS `Category` ( + `id` BIGINT DEFAULT NULL, + `name` TEXT DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `ClassModel` generated from model 'ClassModel' +-- Model for testing model with \"_class\" property +-- + +CREATE TABLE IF NOT EXISTS `ClassModel` ( + `_class` TEXT DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Model for testing model with \"_class\" property'; + +-- +-- Table structure for table `Client` generated from model 'Client' +-- + +CREATE TABLE IF NOT EXISTS `Client` ( + `client` TEXT DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `Dog` generated from model 'Dog' +-- + +CREATE TABLE IF NOT EXISTS `Dog` ( + `className` TEXT NOT NULL, + `color` TEXT, + `breed` TEXT DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `EnumArrays` generated from model 'EnumArrays' +-- + +CREATE TABLE IF NOT EXISTS `EnumArrays` ( + `just_symbol` ENUM('>=', '$') DEFAULT NULL, + `array_enum` JSON DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `Enum_Test` generated from model 'EnumUnderscoreTest' +-- + +CREATE TABLE IF NOT EXISTS `Enum_Test` ( + `enum_string` ENUM('UPPER', 'lower', '') DEFAULT NULL, + `enum_string_required` ENUM('UPPER', 'lower', '') NOT NULL, + `enum_integer` ENUM('1', '-1') DEFAULT NULL, + `enum_number` ENUM('1.1', '-1.2') DEFAULT NULL, + `outerEnum` TEXT DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `File` generated from model 'File' +-- Must be named `File` for test. +-- + +CREATE TABLE IF NOT EXISTS `File` ( + `sourceURI` TEXT DEFAULT NULL COMMENT 'Test capitalization' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Must be named `File` for test.'; + +-- +-- Table structure for table `FileSchemaTestClass` generated from model 'FileSchemaTestClass' +-- + +CREATE TABLE IF NOT EXISTS `FileSchemaTestClass` ( + `file` TEXT DEFAULT NULL, + `files` JSON DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `format_test` generated from model 'formatUnderscoretest' +-- + +CREATE TABLE IF NOT EXISTS `format_test` ( + `integer` TINYINT UNSIGNED DEFAULT NULL, + `int32` TINYINT UNSIGNED DEFAULT NULL, + `int64` BIGINT DEFAULT NULL, + `number` DECIMAL(20, 9) UNSIGNED NOT NULL, + `float` DECIMAL(20, 9) UNSIGNED DEFAULT NULL, + `double` DECIMAL(20, 9) UNSIGNED DEFAULT NULL, + `string` TEXT DEFAULT NULL, + `byte` MEDIUMBLOB NOT NULL, + `binary` MEDIUMBLOB DEFAULT NULL, + `date` DATE NOT NULL, + `dateTime` DATETIME DEFAULT NULL, + `uuid` TEXT DEFAULT NULL, + `password` VARCHAR(64) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `hasOnlyReadOnly` generated from model 'hasOnlyReadOnly' +-- + +CREATE TABLE IF NOT EXISTS `hasOnlyReadOnly` ( + `bar` TEXT DEFAULT NULL, + `foo` TEXT DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `List` generated from model 'List' +-- + +CREATE TABLE IF NOT EXISTS `List` ( + `123-list` TEXT DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `MapTest` generated from model 'MapTest' +-- + +CREATE TABLE IF NOT EXISTS `MapTest` ( + `map_map_of_string` JSON DEFAULT NULL, + `map_of_enum_string` JSON DEFAULT NULL, + `direct_map` JSON DEFAULT NULL, + `indirect_map` JSON DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `MixedPropertiesAndAdditionalPropertiesClass` generated from model 'MixedPropertiesAndAdditionalPropertiesClass' +-- + +CREATE TABLE IF NOT EXISTS `MixedPropertiesAndAdditionalPropertiesClass` ( + `uuid` TEXT DEFAULT NULL, + `dateTime` DATETIME DEFAULT NULL, + `map` JSON DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `Name` generated from model 'Name' +-- Model for testing model name same as property name +-- + +CREATE TABLE IF NOT EXISTS `Name` ( + `name` INT NOT NULL, + `snake_case` INT DEFAULT NULL, + `property` TEXT DEFAULT NULL, + `123Number` INT DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Model for testing model name same as property name'; + +-- +-- Table structure for table `NumberOnly` generated from model 'NumberOnly' +-- + +CREATE TABLE IF NOT EXISTS `NumberOnly` ( + `JustNumber` DECIMAL(20, 9) DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `Order` generated from model 'Order' +-- + +CREATE TABLE IF NOT EXISTS `Order` ( + `id` BIGINT DEFAULT NULL, + `petId` BIGINT DEFAULT NULL, + `quantity` INT DEFAULT NULL, + `shipDate` DATETIME DEFAULT NULL, + `status` ENUM('placed', 'approved', 'delivered') DEFAULT NULL COMMENT 'Order Status', + `complete` TINYINT(1) DEFAULT false +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `OuterComposite` generated from model 'OuterComposite' +-- + +CREATE TABLE IF NOT EXISTS `OuterComposite` ( + `my_number` DECIMAL(20, 9) DEFAULT NULL, + `my_string` TEXT DEFAULT NULL, + `my_boolean` TINYINT(1) DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `Pet` generated from model 'Pet' +-- + +CREATE TABLE IF NOT EXISTS `Pet` ( + `id` BIGINT DEFAULT NULL, + `category` TEXT DEFAULT NULL, + `name` TEXT NOT NULL, + `photoUrls` JSON NOT NULL, + `tags` JSON DEFAULT NULL, + `status` ENUM('available', 'pending', 'sold') DEFAULT NULL COMMENT 'pet status in the store' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `ReadOnlyFirst` generated from model 'ReadOnlyFirst' +-- + +CREATE TABLE IF NOT EXISTS `ReadOnlyFirst` ( + `bar` TEXT DEFAULT NULL, + `baz` TEXT DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `Return` generated from model 'Return' +-- Model for testing reserved words +-- + +CREATE TABLE IF NOT EXISTS `Return` ( + `return` INT DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Model for testing reserved words'; + +-- +-- Table structure for table `Tag` generated from model 'Tag' +-- + +CREATE TABLE IF NOT EXISTS `Tag` ( + `id` BIGINT DEFAULT NULL, + `name` TEXT DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- +-- Table structure for table `User` generated from model 'User' +-- + +CREATE TABLE IF NOT EXISTS `User` ( + `id` BIGINT DEFAULT NULL, + `username` TEXT DEFAULT NULL, + `firstName` TEXT DEFAULT NULL, + `lastName` TEXT DEFAULT NULL, + `email` TEXT DEFAULT NULL, + `password` TEXT DEFAULT NULL, + `phone` TEXT DEFAULT NULL, + `userStatus` INT DEFAULT NULL COMMENT 'User Status' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +