123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- using System;
- using System.Collections.Generic;
- using System.Linq.Expressions;
- using Vit.Linq;
- using Vit.Linq.ExpressionNodes.ComponentModel;
- using Vitorm.Entity;
- using Vitorm.MySql.TranslateService;
- using Vitorm.Sql.SqlTranslate;
- namespace Vitorm.MySql
- {
- public class SqlTranslateService : Vitorm.Sql.SqlTranslate.SqlTranslateService
- {
- public static readonly SqlTranslateService Instance = new SqlTranslateService();
- protected override BaseQueryTranslateService queryTranslateService { get; }
- protected override BaseQueryTranslateService executeUpdateTranslateService { get; }
- protected override BaseQueryTranslateService executeDeleteTranslateService { get; }
- public SqlTranslateService()
- {
- queryTranslateService = new QueryTranslateService(this);
- executeUpdateTranslateService = new ExecuteUpdateTranslateService(this);
- executeDeleteTranslateService = new ExecuteDeleteTranslateService(this);
- }
- /// <summary>
- /// Generates the delimited SQL representation of an identifier (column name, table name, etc.).
- /// </summary>
- /// <param name="identifier">The identifier to delimit.</param>
- /// <returns>
- /// The generated string.
- /// </returns>
- public override string DelimitIdentifier(string identifier) => $"`{EscapeIdentifier(identifier)}`"; // Interpolation okay; strings
- /// <summary>
- /// Generates the escaped SQL representation of an identifier (column name, table name, etc.).
- /// </summary>
- /// <param name="identifier">The identifier to be escaped.</param>
- /// <returns>
- /// The generated string.
- /// </returns>
- public override string EscapeIdentifier(string identifier) => identifier?.Replace("`", "\\`");
- #region EvalExpression
- /// <summary>
- /// read where or value or on
- /// </summary>
- /// <param name="arg"></param>
- /// <returns></returns>
- /// <exception cref="NotSupportedException"></exception>
- /// <param name="node"></param>
- public override string EvalExpression(QueryTranslateArgument arg, ExpressionNode node)
- {
- switch (node.nodeType)
- {
- case NodeType.MethodCall:
- {
- ExpressionNode_MethodCall methodCall = node;
- switch (methodCall.methodName)
- {
- // ##1 ToString
- case nameof(object.ToString):
- {
- return $"cast({EvalExpression(arg, methodCall.@object)} as char)";
- }
- #region ##2 String method: StartsWith EndsWith Contains
- case nameof(string.StartsWith): // String.StartsWith
- {
- var str = methodCall.@object;
- var value = methodCall.arguments[0];
- return $"{EvalExpression(arg, str)} like concat({EvalExpression(arg, value)},'%')";
- }
- case nameof(string.EndsWith): // String.EndsWith
- {
- var str = methodCall.@object;
- var value = methodCall.arguments[0];
- return $"{EvalExpression(arg, str)} like concat('%',{EvalExpression(arg, value)})";
- }
- case nameof(string.Contains) when methodCall.methodCall_typeName == "String": // String.Contains
- {
- var str = methodCall.@object;
- var value = methodCall.arguments[0];
- return $"{EvalExpression(arg, str)} like concat('%',{EvalExpression(arg, value)},'%')";
- }
- #endregion
- }
- break;
- }
- #region Read Value
- case NodeType.Convert:
- {
- // cast( 4.1 as signed)
- ExpressionNode_Convert convert = node;
- Type targetType = convert.valueType?.ToType();
- if (targetType == typeof(object)) return EvalExpression(arg, convert.body);
- // Nullable
- if (targetType.IsGenericType) targetType = targetType.GetGenericArguments()[0];
- string targetDbType = GetColumnDbType(targetType);
- var sourceType = convert.body.Member_GetType();
- if (sourceType != null)
- {
- if (sourceType.IsGenericType) sourceType = sourceType.GetGenericArguments()[0];
- if (targetDbType == GetColumnDbType(sourceType)) return EvalExpression(arg, convert.body);
- }
- if (targetType == typeof(string))
- {
- return $"cast({EvalExpression(arg, convert.body)} as char)";
- }
- return $"cast({EvalExpression(arg, convert.body)} as {targetDbType})";
- }
- case nameof(ExpressionType.Add):
- {
- ExpressionNode_Binary binary = node;
- // ##1 String Add
- if (node.valueType?.ToType() == typeof(string))
- {
- // select ifNull( cast( (userFatherId) as char ) , '' ) from `User`
- return $"CONCAT( {BuildSqlSentence(binary.left)} , {BuildSqlSentence(binary.right)} )";
- string BuildSqlSentence(ExpressionNode node)
- {
- if (node.nodeType == NodeType.Constant)
- {
- ExpressionNode_Constant constant = node;
- if (constant.value == null) return "''";
- else return $"cast( ({EvalExpression(arg, node)}) as char )";
- }
- else
- return $"ifNull( cast( ({EvalExpression(arg, node)}) as char ) , '')";
- }
- }
- // ##2 Numeric Add
- return $"{EvalExpression(arg, binary.left)} + {EvalExpression(arg, binary.right)}";
- }
- case nameof(ExpressionType.Coalesce):
- {
- ExpressionNode_Binary binary = node;
- return $"COALESCE({EvalExpression(arg, binary.left)},{EvalExpression(arg, binary.right)})";
- }
- case nameof(ExpressionType.Conditional):
- {
- // IF(500<1000,true,false)
- ExpressionNode_Conditional conditional = node;
- return $"IF({EvalExpression(arg, conditional.Conditional_GetTest())},{EvalExpression(arg, conditional.Conditional_GetIfTrue())},{EvalExpression(arg, conditional.Conditional_GetIfFalse())})";
- }
- #endregion
- }
- return base.EvalExpression(arg, node);
- }
- #endregion
- #region PrepareCreate
- public override string PrepareTryCreateTable(IEntityDescriptor entityDescriptor)
- {
- /* //sql
- CREATE TABLE IF NOT EXISTS `User` (
- id int PRIMARY KEY AUTO_INCREMENT NOT NULL,
- name varchar(100) DEFAULT NULL,
- birth date DEFAULT NULL,
- fatherId int DEFAULT NULL,
- motherId int DEFAULT NULL
- ) ;
- */
- List<string> sqlFields = new();
- // #1 primary key
- if (entityDescriptor.key != null)
- sqlFields.Add(GetColumnSql(entityDescriptor.key));
- // #2 columns
- entityDescriptor.propertiesWithoutKey?.ForEach(column => sqlFields.Add(GetColumnSql(column)));
- return $@"
- CREATE TABLE IF NOT EXISTS {DelimitTableName(entityDescriptor)} (
- {string.Join(",\r\n ", sqlFields)}
- )";
- string GetColumnSql(IPropertyDescriptor column)
- {
- var isNullable = !column.isKey && column.isNullable;
- var columnDbType = column.columnDbType ?? GetColumnDbType(column);
- var defaultValue = isNullable ? "default null" : "";
- if (column.isIdentity)
- {
- var type = TypeUtil.GetUnderlyingType(column.type);
- if (type == typeof(Guid)) { }
- else defaultValue = "AUTO_INCREMENT";
- }
- var nullable = isNullable ? "" : "not null";
- // https://mysql.net.cn/doc/refman/8.0/en/create-table.html
- /*
- name type primaryKey defaultValue nullable
- id int primary key default null not null/null
- AUTO_INCREMENT
- */
- return $" {DelimitIdentifier(column.columnName)} {columnDbType} {(column.isKey ? "primary key" : "")} {defaultValue} {nullable}";
- }
- }
- public readonly static Dictionary<Type, string> columnDbTypeMap = new()
- {
- [typeof(DateTime)] = "DATETIME",
- [typeof(string)] = "varchar(1000)",
- [typeof(float)] = "FLOAT",
- [typeof(double)] = "DOUBLE",
- [typeof(decimal)] = "DOUBLE",
- [typeof(Int32)] = "INTEGER",
- [typeof(Int16)] = "SMALLINT",
- [typeof(byte)] = "TINYINT",
- [typeof(bool)] = "TINYINT",
- [typeof(Guid)] = "binary(16)",
- };
- protected override string GetColumnDbType(IPropertyDescriptor column)
- {
- Type type = column.type;
- if (column.isKey && column.type == typeof(string))
- {
- // avoid issue: SQL Error [1071] [42000]: Specified key was too long; max key length is 3072 bytes.
- // example "CREATE TABLE test ( `name` varchar(1000) primary key);"
- return "varchar(200)";
- }
- if (column.columnLength.HasValue && type == typeof(string))
- {
- // varchar(1000)
- return $"varchar({(column.columnLength.Value)})";
- }
- return GetColumnDbType(type);
- }
- protected override string GetColumnDbType(Type type)
- {
- var underlyingType = TypeUtil.GetUnderlyingType(type);
- if (columnDbTypeMap.TryGetValue(underlyingType, out var dbType)) return dbType;
- if (underlyingType.Name.ToLower().Contains("int")) return "INTEGER";
- throw new NotSupportedException("unsupported column type:" + underlyingType.Name);
- }
- #endregion
- public override string PrepareTryDropTable(IEntityDescriptor entityDescriptor)
- {
- // drop table if exists `User`;
- return $@"drop table if exists {DelimitTableName(entityDescriptor)};";
- }
- public override (string sql, Func<object, Dictionary<string, object>> GetSqlParams) PrepareAdd(SqlTranslateArgument arg, EAddType addType)
- {
- if (addType == EAddType.identityKey)
- {
- // insert into user(name,fatherId,motherId) values('',0,0); select last_insert_id();
- var entityDescriptor = arg.entityDescriptor;
- var (columnNames, sqlColumnParams, GetSqlParams) = PrepareAdd_Columns(arg, entityDescriptor.propertiesWithoutKey);
- string sql = $@"insert into {DelimitTableName(entityDescriptor)}({string.Join(",", columnNames)}) values({string.Join(",", sqlColumnParams)});";
- // get generated id
- sql += "select last_insert_id();";
- return (sql, GetSqlParams);
- }
- else
- {
- // insert into user(name,fatherId,motherId) values('',0,0);
- var entityDescriptor = arg.entityDescriptor;
- var (columnNames, sqlColumnParams, GetSqlParams) = PrepareAdd_Columns(arg, entityDescriptor.properties);
- string sql = $@"insert into {DelimitTableName(entityDescriptor)}({string.Join(",", columnNames)}) values({string.Join(",", sqlColumnParams)});";
- return (sql, GetSqlParams);
- }
- }
- }
- }
|