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