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