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