001/*
002 *  Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
003 *  <p>
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *  <p>
008 *  http://www.apache.org/licenses/LICENSE-2.0
009 *  <p>
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package com.mybatisflex.core.dialect.impl;
017
018import com.mybatisflex.core.constant.SqlConsts;
019import com.mybatisflex.core.dialect.KeywordWrap;
020import com.mybatisflex.core.dialect.LimitOffsetProcessor;
021import com.mybatisflex.core.dialect.OperateType;
022import com.mybatisflex.core.row.Row;
023import com.mybatisflex.core.row.RowCPI;
024import com.mybatisflex.core.table.TableInfo;
025import com.mybatisflex.core.util.CollectionUtil;
026import com.mybatisflex.core.util.SqlUtil;
027import com.mybatisflex.core.util.StringUtil;
028
029import java.util.List;
030import java.util.Map;
031import java.util.Set;
032import java.util.StringJoiner;
033
034import static com.mybatisflex.core.constant.SqlConsts.*;
035
036/**
037 * @author michael
038 */
039public class OracleDialect extends CommonsDialectImpl {
040
041    //https://docs.oracle.com/cd/A97630_01/appdev.920/a42525/apb.htm
042    public static final Set<String> keywords = CollectionUtil.newHashSet(
043        "ACCESS", "ELSE", "MODIFY", "START", "ADD", "EXCLUSIVE", "NOAUDIT", "SELECT",
044        "ALL", "EXISTS", "NOCOMPRESS", "SESSION", "ALTER", "FILE", "NOT", "SET", "AND", "FLOAT",
045        "NOTFOUND", "SHARE", "ANY", "FOR", "NOWAIT", "SIZE", "ARRAYLEN", "FROM", "NULL", "SMALLINT",
046        "AS", "GRANT", "NUMBER", "SQLBUF", "ASC", "GROUP", "OF", "SUCCESSFUL", "AUDIT", "HAVING",
047        "OFFLINE", "SYNONYM", "BETWEEN", "IDENTIFIED", "ON", "SYSDATE", "BY", "IMMEDIATE", "ONLINE",
048        "TABLE", "CHAR", "IN", "OPTION", "THEN", "CHECK", "INCREMENT", "OR", "TO", "CLUSTER", "INDEX",
049        "ORDER", "TRIGGER", "COLUMN", "INITIAL", "PCTFREE", "UID", "COMMENT", "INSERT", "PRIOR",
050        "UNION", "COMPRESS", "INTEGER", "PRIVILEGES", "UNIQUE", "CONNECT", "INTERSECT", "PUBLIC",
051        "UPDATE", "CREATE", "INTO", "RAW", "USER", "CURRENT", "IS", "RENAME", "VALIDATE", "DATE", "LEVEL",
052        "RESOURCE", "VALUES", "DECIMAL", "LIKE", "REVOKE", "VARCHAR", "DEFAULT", "LOCK", "ROW", "VARCHAR2",
053        "DELETE", "LONG", "ROWID", "VIEW", "DESC", "MAXEXTENTS", "ROWLABEL", "WHENEVER", "DISTINCT", "MINUS",
054        "ROWNUM", "WHERE", "DROP", "MODE", "ROWS", "WITH", "ADMIN", "CURSOR", "FOUND", "MOUNT", "AFTER", "CYCLE",
055        "FUNCTION", "NEXT", "ALLOCATE", "DATABASE", "GO", "NEW", "ANALYZE", "DATAFILE", "GOTO", "NOARCHIVELOG",
056        "ARCHIVE", "DBA", "GROUPS", "NOCACHE", "ARCHIVELOG", "DEC", "INCLUDING", "NOCYCLE", "AUTHORIZATION",
057        "DECLARE", "INDICATOR", "NOMAXVALUE", "AVG", "DISABLE", "INITRANS", "NOMINVALUE", "BACKUP", "DISMOUNT",
058        "INSTANCE", "NONE", "BEGIN", "DOUBLE", "INT", "NOORDER", "BECOME", "DUMP", "KEY", "NORESETLOGS", "BEFORE",
059        "EACH", "LANGUAGE", "NORMAL", "BLOCK", "ENABLE", "LAYER", "NOSORT", "BODY", "END", "LINK", "NUMERIC", "CACHE",
060        "ESCAPE", "LISTS", "OFF", "CANCEL", "EVENTS", "LOGFILE", "OLD", "CASCADE", "EXCEPT", "MANAGE", "ONLY", "CHANGE",
061        "EXCEPTIONS", "MANUAL", "OPEN", "CHARACTER", "EXEC", "MAX", "OPTIMAL", "CHECKPOINT", "EXPLAIN", "MAXDATAFILES",
062        "OWN", "CLOSE", "EXECUTE", "MAXINSTANCES", "PACKAGE", "COBOL", "EXTENT", "MAXLOGFILES", "PARALLEL", "COMMIT",
063        "EXTERNALLY", "MAXLOGHISTORY", "PCTINCREASE", "COMPILE", "FETCH", "MAXLOGMEMBERS", "PCTUSED", "CONSTRAINT",
064        "FLUSH", "MAXTRANS", "PLAN", "CONSTRAINTS", "FREELIST", "MAXVALUE", "PLI", "CONTENTS", "FREELISTS", "MIN",
065        "PRECISION", "CONTINUE", "FORCE", "MINEXTENTS", "PRIMARY", "CONTROLFILE", "FOREIGN", "MINVALUE", "PRIVATE",
066        "COUNT", "FORTRAN", "MODULE", "PROCEDURE", "PROFILE", "SAVEPOINT", "SQLSTATE", "TRACING", "QUOTA", "SCHEMA",
067        "STATEMENT_ID", "TRANSACTION", "READ", "SCN", "STATISTICS", "TRIGGERS", "REAL", "SECTION", "STOP", "TRUNCATE",
068        "RECOVER", "SEGMENT", "STORAGE", "UNDER", "REFERENCES", "SEQUENCE", "SUM", "UNLIMITED", "REFERENCING", "SHARED",
069        "SWITCH", "UNTIL", "RESETLOGS", "SNAPSHOT", "SYSTEM", "USE", "RESTRICTED", "SOME", "TABLES", "USING", "REUSE",
070        "SORT", "TABLESPACE", "WHEN", "ROLE", "SQL", "TEMPORARY", "WRITE", "ROLES", "SQLCODE", "THREAD", "WORK", "ROLLBACK",
071        "SQLERROR", "TIME", "ABORT", "BETWEEN", "CRASH", "DIGITS", "ACCEPT", "BINARY_INTEGER", "CREATE", "DISPOSE", "ACCESS",
072        "BODY", "CURRENT", "DISTINCT", "ADD", "BOOLEAN", "CURRVAL", "DO", "ALL", "BY", "CURSOR", "DROP", "ALTER", "CASE", "DATABASE",
073        "ELSE", "AND", "CHAR", "DATA_BASE", "ELSIF", "ANY", "CHAR_BASE", "DATE", "END", "ARRAY", "CHECK", "DBA", "ENTRY", "ARRAYLEN",
074        "CLOSE", "DEBUGOFF", "EXCEPTION", "AS", "CLUSTER", "DEBUGON", "EXCEPTION_INIT", "ASC", "CLUSTERS", "DECLARE", "EXISTS",
075        "ASSERT", "COLAUTH", "DECIMAL", "EXIT", "ASSIGN", "COLUMNS", "DEFAULT", "FALSE", "AT", "COMMIT", "DEFINITION", "FETCH",
076        "AUTHORIZATION", "COMPRESS", "DELAY", "FLOAT", "AVG", "CONNECT", "DELETE", "FOR", "BASE_TABLE", "CONSTANT", "DELTA", "FORM",
077        "BEGIN", "COUNT", "DESC", "FROM", "FUNCTION", "NEW", "RELEASE", "SUM", "GENERIC", "NEXTVAL", "REMR", "TABAUTH",
078        "GOTO", "NOCOMPRESS", "RENAME", "TABLE", "GRANT", "NOT", "RESOURCE", "TABLES", "GROUP", "NULL", "RETURN", "TASK", "HAVING",
079        "NUMBER", "REVERSE", "TERMINATE", "IDENTIFIED", "NUMBER_BASE", "REVOKE", "THEN", "IF", "OF", "ROLLBACK", "TO", "IN", "ON",
080        "ROWID", "TRUE", "INDEX", "OPEN", "ROWLABEL", "TYPE", "INDEXES", "OPTION", "ROWNUM", "UNION", "INDICATOR", "OR", "ROWTYPE",
081        "UNIQUE", "INSERT", "ORDER", "RUN", "UPDATE", "INTEGER", "OTHERS", "SAVEPOINT", "USE", "INTERSECT", "OUT", "SCHEMA", "VALUES",
082        "INTO", "PACKAGE", "SELECT", "VARCHAR", "IS", "PARTITION", "SEPARATE", "VARCHAR2", "LEVEL", "PCTFREE", "SET", "VARIANCE",
083        "LIKE", "POSITIVE", "SIZE", "VIEW", "LIMITED", "PRAGMA", "SMALLINT", "VIEWS", "LOOP", "PRIOR", "SPACE", "WHEN", "MAX", "PRIVATE",
084        "SQL", "WHERE", "MIN", "PROCEDURE", "SQLCODE", "WHILE", "MINUS", "PUBLIC", "SQLERRM", "WITH", "MLSLABEL", "RAISE", "START",
085        "WORK", "MOD", "RANGE", "STATEMENT", "XOR", "MODE", "REAL", "STDDEV", "NATURAL", "RECORD", "SUBTYPE", "GEN", "KP", "L",
086        "NA", "NC", "ND", "NL", "NM", "NR", "NS", "NT", "NZ", "TTC", "UPI", "O", "S", "XA"
087
088    );
089
090    public OracleDialect() {
091        this(LimitOffsetProcessor.ORACLE);
092    }
093
094    public OracleDialect(LimitOffsetProcessor limitOffsetProcessor) {
095        this(new KeywordWrap(false, true, keywords, "\"", "\""), limitOffsetProcessor);
096    }
097
098    public OracleDialect(KeywordWrap keywordWrap, LimitOffsetProcessor limitOffsetProcessor) {
099        super(keywordWrap, limitOffsetProcessor);
100    }
101
102    @Override
103    public String forInsertEntityBatch(TableInfo tableInfo, List<?> entities) {
104        /**
105         * INSERT ALL
106         *    INTO t (col1, col2, col3) VALUES ('val1_1', 'val1_2', 'val1_3')
107         *    INTO t (col1, col2, col3) VALUES ('val2_1', 'val2_2', 'val2_3')
108         *    INTO t (col1, col2, col3) VALUES ('val3_1', 'val3_2', 'val3_3')
109         *    .
110         *    .
111         *    .
112         * SELECT 1 FROM DUAL;
113         */
114        StringBuilder sql = new StringBuilder();
115        sql.append(INSERT_ALL);
116        String[] insertColumns = tableInfo.obtainInsertColumns(null, false);
117        String[] warpedInsertColumns = new String[insertColumns.length];
118        for (int i = 0; i < insertColumns.length; i++) {
119            warpedInsertColumns[i] = wrap(insertColumns[i]);
120        }
121
122
123        Map<String, String> onInsertColumns = tableInfo.getOnInsertColumns();
124        for (int i = 0; i < entities.size(); i++) {
125            sql.append(INTO).append(tableInfo.getWrapSchemaAndTableName(this, OperateType.INSERT));
126            sql.append(BLANK).append(BRACKET_LEFT).append(StringUtil.join(DELIMITER, warpedInsertColumns)).append(BRACKET_RIGHT);
127            sql.append(VALUES);
128
129            StringJoiner stringJoiner = new StringJoiner(DELIMITER, BRACKET_LEFT, BRACKET_RIGHT);
130            for (String insertColumn : insertColumns) {
131                if (onInsertColumns != null && onInsertColumns.containsKey(insertColumn)) {
132                    //直接读取 onInsert 配置的值,而不用 "?" 代替
133                    stringJoiner.add(onInsertColumns.get(insertColumn));
134                } else {
135                    stringJoiner.add(PLACEHOLDER);
136                }
137            }
138            sql.append(stringJoiner);
139        }
140
141        return sql.append(INSERT_ALL_END).toString();
142    }
143
144
145    @Override
146    public String forInsertBatchWithFirstRowColumns(String schema, String tableName, List<Row> rows) {
147        /**
148         * INSERT ALL
149         *    INTO t (col1, col2, col3) VALUES ('val1_1', 'val1_2', 'val1_3')
150         *    INTO t (col1, col2, col3) VALUES ('val2_1', 'val2_2', 'val2_3')
151         *    INTO t (col1, col2, col3) VALUES ('val3_1', 'val3_2', 'val3_3')
152         *    .
153         *    .
154         *    .
155         * SELECT 1 FROM DUAL;
156         */
157        StringBuilder fields = new StringBuilder();
158        Row firstRow = rows.get(0);
159        Set<String> attrs = RowCPI.getInsertAttrs(firstRow);
160        int index = 0;
161        for (String column : attrs) {
162            fields.append(wrap(column));
163            if (index != attrs.size() - 1) {
164                fields.append(SqlConsts.DELIMITER);
165            }
166            index++;
167        }
168
169        StringBuilder sql = new StringBuilder();
170        sql.append(INSERT_ALL);
171
172        String table = getRealTable(tableName, OperateType.INSERT);
173        String tableNameWrap = StringUtil.isNotBlank(schema)
174            ? wrap(getRealSchema(schema, table, OperateType.INSERT)) + REFERENCE + wrap(table)
175            : wrap(table);
176        String questionStrings = SqlUtil.buildSqlParamPlaceholder(attrs.size());
177
178        for (int i = 0; i < rows.size(); i++) {
179            sql.append(INTO).append(tableNameWrap);
180            sql.append(BLANK).append(BRACKET_LEFT).append(fields).append(BRACKET_RIGHT);
181            sql.append(VALUES).append(questionStrings);
182        }
183
184        return sql.append(INSERT_ALL_END).toString();
185    }
186
187}