OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 180 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
17 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
182 ilm 4
 * Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
17 ilm 5
 *
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
7
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
8
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
13
 
14
 package org.openconcerto.sql.model;
15
 
16
import org.openconcerto.sql.model.SQLField.Properties;
83 ilm 17
import org.openconcerto.sql.model.graph.Link.Rule;
67 ilm 18
import org.openconcerto.sql.model.graph.TablesMap;
83 ilm 19
import org.openconcerto.sql.utils.ChangeTable.ClauseType;
144 ilm 20
import org.openconcerto.sql.utils.ChangeTable.DeferredClause;
83 ilm 21
import org.openconcerto.sql.utils.SQLUtils;
22
import org.openconcerto.utils.CollectionUtils;
17 ilm 23
import org.openconcerto.utils.FileUtils;
83 ilm 24
import org.openconcerto.utils.ListMap;
25
import org.openconcerto.utils.ProcessStreams;
26
import org.openconcerto.utils.RTInterruptedException;
27
import org.openconcerto.utils.StringUtils;
17 ilm 28
import org.openconcerto.utils.Tuple2;
83 ilm 29
import org.openconcerto.utils.cc.ITransformer;
182 ilm 30
import org.openconcerto.xml.XMLCodecUtils;
17 ilm 31
 
182 ilm 32
import java.beans.DefaultPersistenceDelegate;
83 ilm 33
import java.io.BufferedReader;
34
import java.io.BufferedWriter;
17 ilm 35
import java.io.File;
83 ilm 36
import java.io.FileInputStream;
37
import java.io.FileOutputStream;
17 ilm 38
import java.io.IOException;
83 ilm 39
import java.io.InputStreamReader;
40
import java.io.OutputStreamWriter;
17 ilm 41
import java.math.BigDecimal;
83 ilm 42
import java.nio.charset.Charset;
17 ilm 43
import java.sql.Blob;
44
import java.sql.Clob;
45
import java.sql.SQLException;
46
import java.sql.Timestamp;
83 ilm 47
import java.sql.Types;
48
import java.util.Arrays;
49
import java.util.BitSet;
142 ilm 50
import java.util.IdentityHashMap;
17 ilm 51
import java.util.List;
52
import java.util.Map;
53
import java.util.Set;
54
import java.util.regex.Pattern;
55
 
182 ilm 56
public class SQLSyntaxMS extends SQLSyntax {
17 ilm 57
 
142 ilm 58
    static private final IdentityHashMap<String, String> DATE_SPECS;
59
 
60
    static {
61
        DATE_SPECS = new IdentityHashMap<String, String>();
62
        DATE_SPECS.put(DateProp.YEAR, "yyyy");
63
        DATE_SPECS.put(DateProp.MONTH_NAME, "MMMM");
64
        DATE_SPECS.put(DateProp.MONTH_NUMBER, "MM");
65
        DATE_SPECS.put(DateProp.DAY_IN_MONTH, "dd");
66
        DATE_SPECS.put(DateProp.DAY_NAME_IN_WEEK, "dddd");
67
        DATE_SPECS.put(DateProp.HOUR, "HH");
68
        DATE_SPECS.put(DateProp.MINUTE, "mm");
69
        DATE_SPECS.put(DateProp.SECOND, "ss");
70
        DATE_SPECS.put(DateProp.MICROSECOND, "ffffff");
182 ilm 71
 
72
        XMLCodecUtils.register(SQLSyntaxMS.class, new DefaultPersistenceDelegate(new String[] {}));
142 ilm 73
    }
74
 
182 ilm 75
    public SQLSyntaxMS() {
142 ilm 76
        super(SQLSystem.MSSQL, DATE_SPECS);
83 ilm 77
        this.typeNames.addAll(Boolean.class, "bit");
78
        // tinyint is unsigned
79
        this.typeNames.addAll(Short.class, "smallint", "tinyint");
80
        this.typeNames.addAll(Integer.class, "int");
81
        this.typeNames.addAll(Long.class, "bigint");
82
        this.typeNames.addAll(BigDecimal.class, "decimal", "numeric", "smallmoney", "money");
83
        this.typeNames.addAll(Float.class, "real");
142 ilm 84
        this.typeNames.addAll(Double.class, "double precision", "float");
85
        this.typeNames.addAll(Timestamp.class, "datetime2", "datetime", "smalldatetime");
83 ilm 86
        this.typeNames.addAll(java.sql.Date.class, "date");
87
        this.typeNames.addAll(java.sql.Time.class, "time");
88
        this.typeNames.addAll(Blob.class, "image",
142 ilm 89
                // byte[]
17 ilm 90
                "varbinary", "binary");
83 ilm 91
        this.typeNames.addAll(Clob.class, "text", "ntext", "unitext");
92
        this.typeNames.addAll(String.class, "char", "varchar", "nchar", "nvarchar", "unichar", "univarchar");
17 ilm 93
    }
94
 
95
    @Override
142 ilm 96
    public final String quoteString(String s) {
97
        final String res = super.quoteString(s);
98
        if (s == null)
99
            return res;
100
        // only use escape form if needed (=> equals with other systems most of the time)
101
        boolean simpleASCII = true;
102
        final int l = s.length();
103
        for (int i = 0; simpleASCII && i < l; i++) {
104
            final char c = s.charAt(i);
105
            simpleASCII = c <= 0xFF;
106
        }
107
        // see http://msdn.microsoft.com/fr-fr/library/ms191200(v=sql.105).aspx
108
        return simpleASCII ? res : "N" + res;
109
    }
110
 
111
    @Override
132 ilm 112
    public int getMaximumIdentifierLength() {
113
        // https://msdn.microsoft.com/en-us/library/ms143432.aspx
114
        return 128;
115
    }
116
 
117
    @Override
83 ilm 118
    public String getInitSystemRoot() {
119
        final String sql;
120
        try {
121
            final String fileContent = FileUtils.readUTF8(SQLSyntaxPG.class.getResourceAsStream("mssql-functions.sql"));
122
            sql = fileContent.replace("${rootName}", SQLBase.quoteIdentifier("dbo"));
123
        } catch (IOException e) {
124
            throw new IllegalStateException("cannot read functions", e);
125
        }
126
        return sql;
127
    }
128
 
129
    @Override
17 ilm 130
    public boolean isAuto(SQLField f) {
131
        return f.getType().getJavaType() == Integer.class && "YES".equals(f.getMetadata("IS_AUTOINCREMENT"));
132
    }
133
 
134
    @Override
135
    public String getAuto() {
136
        return " int IDENTITY";
137
    }
138
 
139
    @Override
83 ilm 140
    public int getMaximumVarCharLength() {
141
        // http://msdn.microsoft.com/en-us/library/ms176089(v=sql.105).aspx
142
        return 8000;
143
    }
144
 
145
    @Override
17 ilm 146
    public String transfDefaultJDBC2SQL(SQLField f) {
147
        final Object def = f.getDefaultValue();
148
        if (def == null)
149
            return null;
150
 
151
        // remove parentheses from ((1))
152
        String stringDef = def.toString();
153
        while (stringDef.charAt(0) == '(' && stringDef.charAt(stringDef.length() - 1) == ')')
154
            stringDef = stringDef.substring(1, stringDef.length() - 1);
155
 
156
        if (f.getType().getJavaType() == Boolean.class) {
157
            return stringDef.equals("'true'") ? "true" : "false";
158
        } else {
159
            return stringDef;
160
        }
161
    }
162
 
163
    @Override
164
    protected String transfDefault(SQLField f, String castless) {
165
        if (castless != null && f.getType().getJavaType() == Boolean.class) {
166
            // yes MS has no true/false keywords
167
            return castless.equals("TRUE") ? "'true'" : "'false'";
168
        } else
169
            return castless;
170
    }
171
 
172
    @Override
83 ilm 173
    protected String getRuleSQL(final Rule r) {
174
        // MSSQL doesn't support RESTRICT
175
        return (r.equals(Rule.RESTRICT) ? Rule.NO_ACTION : r).asString();
176
    }
177
 
178
    @Override
17 ilm 179
    public String disableFKChecks(DBRoot b) {
180
        return fkChecks(b, false);
181
    }
182
 
183
    private String fkChecks(final DBRoot b, final boolean enable) {
184
        final String s = enable ? "with check check constraint all" : "nocheck constraint all";
185
        return "exec sp_MSforeachtable @command1 = 'ALTER TABLE ? " + s + "' , @whereand = " +
186
        //
142 ilm 187
                quoteString("and schema_id = SCHEMA_ID( " + quoteString(b.getName()) + " )");
17 ilm 188
    }
189
 
190
    @Override
191
    public String enableFKChecks(DBRoot b) {
192
        return fkChecks(b, true);
193
    }
194
 
83 ilm 195
    @Override
196
    public List<Map<String, Object>> getIndexInfo(SQLTable t) throws SQLException {
197
        final String query = "SELECT NULL AS \"TABLE_CAT\", schema_name(t.schema_id) as \"TABLE_SCHEM\", t.name as \"TABLE_NAME\",\n" +
198
        //
199
                "~idx.is_unique as \"NON_UNIQUE\", NULL AS \"INDEX_QUALIFIER\", idx.name as \"INDEX_NAME\", NULL as \"TYPE\",\n" +
200
                //
201
                "indexCols.key_ordinal as \"ORDINAL_POSITION\", cols.name as \"COLUMN_NAME\",\n" +
202
                //
203
                "case when indexCols.is_descending_key = 1 then 'D' else 'A' end as \"ASC_OR_DESC\", null as \"CARDINALITY\", null as \"PAGES\",\n" +
204
                //
205
                "filter_definition as \"FILTER_CONDITION\"\n" +
206
                //
207
                "  FROM [test].[sys].[objects] t\n" +
208
                //
209
                "  join [test].[sys].[indexes] idx on idx.object_id = t.object_id\n" +
210
                //
211
                "  join [test].[sys].[index_columns] indexCols on idx.index_id = indexCols.index_id and idx.object_id = indexCols.object_id\n" +
212
                //
213
                "  join [test].[sys].[columns] cols on t.object_id = cols.object_id and cols.column_id = indexCols.column_id \n" +
214
                //
142 ilm 215
                "  where schema_name(t.schema_id) = " + quoteString(t.getSchema().getName()) + " and t.name = " + quoteString(t.getName()) + "\n"
83 ilm 216
                //
217
                + "ORDER BY \"NON_UNIQUE\", \"TYPE\", \"INDEX_NAME\", \"ORDINAL_POSITION\";";
218
        // don't cache since we don't listen on system tables
219
        return (List<Map<String, Object>>) t.getDBSystemRoot().getDataSource().execute(query, new IResultSetHandler(SQLDataSource.MAP_LIST_HANDLER, false));
220
    }
221
 
17 ilm 222
    @SuppressWarnings("unchecked")
223
    @Override
224
    public Map<String, Object> normalizeIndexInfo(final Map m) {
225
        // genuine MS driver
226
        if (getSystem().getJDBCName().equals("sqlserver"))
227
            m.put("NON_UNIQUE", ((Number) m.get("NON_UNIQUE")).intValue() != 0);
228
        return m;
229
    }
230
 
231
    @Override
232
    public String getDropIndex(String name, SQLName tableName) {
233
        return "DROP INDEX " + SQLBase.quoteIdentifier(name) + " on " + tableName.quote() + ";";
234
    }
235
 
83 ilm 236
    @Override
237
    public boolean isUniqueException(SQLException exn) {
238
        return SQLUtils.findWithSQLState(exn).getErrorCode() == 2601;
17 ilm 239
    }
142 ilm 240
 
132 ilm 241
    @Override
242
    public boolean isDeadLockException(SQLException exn) {
243
        return SQLUtils.findWithSQLState(exn).getErrorCode() == 1205;
244
    }
17 ilm 245
 
246
    @Override
182 ilm 247
    public boolean isTableNotFoundException(Exception exn) {
248
        // TODO
249
        throw new UnsupportedOperationException();
250
    }
251
 
252
    @Override
253
    public String getSetLockTimeoutQuery(int millis) {
254
        return "SET LOCK_TIMEOUT " + millis;
255
    }
256
 
257
    @Override
258
    public String getShowLockTimeoutQuery() {
259
        return "SELECT @@LOCK_TIMEOUT";
260
    }
261
 
262
    @Override
83 ilm 263
    public Map<ClauseType, List<String>> getAlterField(SQLField f, Set<Properties> toAlter, String type, String defaultVal, Boolean nullable) {
264
        final ListMap<ClauseType, String> res = new ListMap<ClauseType, String>();
265
        if (toAlter.contains(Properties.TYPE) || toAlter.contains(Properties.NULLABLE)) {
266
            final String newType = toAlter.contains(Properties.TYPE) ? type : getType(f);
17 ilm 267
            final boolean newNullable = toAlter.contains(Properties.NULLABLE) ? nullable : getNullable(f);
83 ilm 268
            res.add(ClauseType.ALTER_COL, "ALTER COLUMN " + f.getQuotedName() + " " + getFieldDecl(newType, null, newNullable));
17 ilm 269
        }
83 ilm 270
        if (toAlter.contains(Properties.DEFAULT)) {
271
            final Constraint existingConstraint = f.getTable().getConstraint(ConstraintType.DEFAULT, Arrays.asList(f.getName()));
272
            if (existingConstraint != null) {
273
                res.add(ClauseType.DROP_CONSTRAINT, "DROP CONSTRAINT " + SQLBase.quoteIdentifier(existingConstraint.getName()));
274
            }
275
            if (defaultVal != null) {
276
                res.add(ClauseType.ADD_CONSTRAINT, "ADD DEFAULT " + defaultVal + " FOR " + f.getQuotedName());
277
            }
278
        }
17 ilm 279
        return res;
280
    }
281
 
282
    @Override
83 ilm 283
    public String getRenameTable(SQLName table, String newName) {
284
        return "sp_rename " + SQLBase.quoteStringStd(table.quote()) + ", " + SQLBase.quoteStringStd(newName);
285
    }
286
 
287
    @Override
93 ilm 288
    public String getDropTable(SQLName name, boolean ifExists, boolean restrict) {
289
        // doesn't support cascade
290
        if (!restrict)
291
            return null;
292
        if (!ifExists) {
293
            return super.getDropTable(name, ifExists, restrict);
294
        } else {
295
            final String quoted = name.quote();
296
            return "IF OBJECT_ID(" + SQLBase.quoteStringStd(quoted) + ", 'U') IS NOT NULL DROP TABLE " + quoted;
297
        }
83 ilm 298
    }
299
 
300
    @Override
17 ilm 301
    public String getDropRoot(String name) {
83 ilm 302
        // Only works if getInitSystemRoot() was executed
17 ilm 303
        // http://ranjithk.com/2010/01/31/script-to-drop-all-objects-of-a-schema/
73 ilm 304
        return "exec CleanUpSchema " + SQLBase.quoteStringStd(name) + ", 'w' ;";
17 ilm 305
    }
306
 
307
    @Override
308
    public String getCreateRoot(String name) {
73 ilm 309
        return "CREATE SCHEMA " + SQLBase.quoteIdentifier(name) + " ;";
17 ilm 310
    }
311
 
312
    @Override
313
    protected Tuple2<Boolean, String> getCast() {
314
        return null;
315
    }
316
 
83 ilm 317
    @Override
318
    public void _loadData(final File f, final SQLTable t) throws IOException {
319
        final String data = FileUtils.readUTF8(f);
320
        final File temp = File.createTempFile(FileUtils.sanitize("mssql_loadData_" + t.getName()), ".txt");
17 ilm 321
 
83 ilm 322
        // no we cant't use UTF16 since Java write BE and MS ignores the BOM, always using LE.
323
        final BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(temp), Charset.forName("x-UTF-16LE-BOM")));
17 ilm 324
 
83 ilm 325
        final List<SQLField> fields = t.getOrderedFields();
326
        final int fieldsCount = fields.size();
327
        final BitSet booleanFields = new BitSet(fieldsCount);
328
        int fieldIndex = 0;
329
        for (final SQLField field : fields) {
330
            final int type = field.getType().getType();
331
            booleanFields.set(fieldIndex++, type == Types.BOOLEAN || type == Types.BIT);
332
        }
333
        fieldIndex = 0;
17 ilm 334
 
83 ilm 335
        try {
336
            // skip fields names
337
            int i = data.indexOf('\n') + 1;
338
            while (i < data.length()) {
339
                final String twoChars = i + 2 <= data.length() ? data.substring(i, i + 2) : null;
340
                if ("\\N".equals(twoChars)) {
341
                    i += 2;
342
                } else if ("\"\"".equals(twoChars)) {
343
                    writer.write("\0");
344
                    i += 2;
345
                } else {
346
                    final Tuple2<String, Integer> unDoubleQuote = StringUtils.unDoubleQuote(data, i);
347
                    String unquoted = unDoubleQuote.get0();
348
                    if (booleanFields.get(fieldIndex)) {
349
                        if (unquoted.equalsIgnoreCase("false")) {
350
                            unquoted = "0";
351
                        } else if (unquoted.equalsIgnoreCase("true")) {
352
                            unquoted = "1";
353
                        }
354
                    }
355
                    writer.write(unquoted);
356
                    i = unDoubleQuote.get1();
357
                }
358
                fieldIndex++;
359
                if (i < data.length()) {
360
                    final char c = data.charAt(i);
361
                    if (c == ',') {
362
                        writer.write(FIELD_DELIM);
363
                        i++;
364
                    } else if (c == '\n') {
365
                        writer.write(ROW_DELIM);
366
                        i++;
367
                        if (fieldIndex != fieldsCount)
368
                            throw new IOException("Expected " + fieldsCount + " fields but got : " + fieldIndex);
369
                        fieldIndex = 0;
370
                    } else {
371
                        throw new IOException("Unexpected character after field : " + c);
372
                    }
373
                }
374
            }
375
            if (fieldIndex != 0 && fieldIndex != fieldsCount)
376
                throw new IOException("Expected " + fieldsCount + " fields but got : " + fieldIndex);
377
        } finally {
378
            writer.close();
379
        }
17 ilm 380
 
83 ilm 381
        execute_bcp(t, false, temp);
382
        temp.delete();
17 ilm 383
 
83 ilm 384
        // MAYBE when on localhost, remove the bcp requirement (OTOH bcp should already be
385
        // installed, just perhaps not in the path)
386
        // checkServerLocalhost(t);
387
        // "bulk insert " + t.getSQL() + " from " + b.quoteString(temp.getAbsolutePath()) +
388
        // " with ( DATAFILETYPE='widechar', FIELDTERMINATOR = " + b.quoteString(FIELD_DELIM)
389
        // + ", ROWTERMINATOR= " + b.quoteString(ROW_DELIM) +
390
        // ", FIRSTROW=1, KEEPIDENTITY, KEEPNULLS ) ;"
391
    }
17 ilm 392
 
83 ilm 393
    private static final String FIELD_DELIM = "<|!!|>";
394
    private static final String ROW_DELIM = "...#~\n~#...";
17 ilm 395
 
83 ilm 396
    protected void execute_bcp(final SQLTable t, final boolean dump, final File f) throws IOException {
397
        final ProcessBuilder pb = new ProcessBuilder("bcp");
398
        pb.command().add(t.getSQLName().quote());
399
        pb.command().add(dump ? "out" : "in");
400
        pb.command().add(f.getAbsolutePath());
401
        // UTF-16LE with a BOM
402
        pb.command().add("-w");
403
        pb.command().add("-t" + FIELD_DELIM);
404
        pb.command().add("-r" + ROW_DELIM);
405
        // needed if table name is a keyword (e.g. RIGHT)
406
        pb.command().add("-q");
407
        pb.command().add("-S" + t.getServer().getName());
408
        pb.command().add("-U" + t.getDBSystemRoot().getDataSource().getUsername());
409
        pb.command().add("-P" + t.getDBSystemRoot().getDataSource().getPassword());
410
        if (!dump) {
411
            // retain null
412
            pb.command().add("-k");
413
            // keep identity
414
            pb.command().add("-E");
415
        }
17 ilm 416
 
180 ilm 417
        final Process p = ProcessStreams.redirect(pb).start();
83 ilm 418
        try {
419
            final int returnCode = p.waitFor();
420
            if (returnCode != 0)
421
                throw new IOException("Did not finish correctly : " + returnCode + "\n" + pb.command());
422
        } catch (InterruptedException e) {
423
            throw new RTInterruptedException(e);
424
        }
425
    }
17 ilm 426
 
83 ilm 427
    // For bcp : http://www.microsoft.com/en-us/download/details.aspx?id=16978
428
    @Override
429
    protected void _storeData(final SQLTable t, final File f) throws IOException {
430
        final File tmpFile = File.createTempFile(FileUtils.sanitize("mssql_dump_" + t.getName()), ".dat");
431
        execute_bcp(t, true, tmpFile);
432
        final int readerBufferSize = 32768;
433
        final BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(tmpFile), StringUtils.UTF16), readerBufferSize);
434
        final List<SQLField> orderedFields = t.getOrderedFields();
435
        final int fieldsCount = orderedFields.size();
436
        final String cols = CollectionUtils.join(orderedFields, ",", new ITransformer<SQLField, String>() {
437
            @Override
438
            public String transformChecked(SQLField input) {
439
                return SQLBase.quoteIdentifier(input.getName());
440
            }
441
        });
442
        final FileOutputStream outs = new FileOutputStream(f);
443
        BufferedWriter writer = null;
444
        try {
445
            writer = new BufferedWriter(new OutputStreamWriter(outs, StringUtils.UTF8));
446
            writer.write(cols);
447
            writer.write('\n');
448
            final StringBuilder sb = new StringBuilder(readerBufferSize * 2);
449
            String row = readUntil(reader, sb, ROW_DELIM);
450
            final Pattern fieldPattern = Pattern.compile(FIELD_DELIM, Pattern.LITERAL);
451
            while (row != null) {
452
                if (row.length() > 0) {
453
                    // -1 to have every (even empty) field
454
                    final String[] fields = fieldPattern.split(row, -1);
455
                    if (fields.length != fieldsCount)
456
                        throw new IOException("Invalid fields count, expected " + fieldsCount + " but was " + fields.length + "\n" + row);
457
                    int i = 0;
458
                    for (final String field : fields) {
459
                        final String quoted;
460
                        if (field.length() == 0) {
461
                            quoted = "\\N";
462
                        } else if (field.equals("\0")) {
463
                            quoted = "\"\"";
464
                        } else {
465
                            quoted = StringUtils.doubleQuote(field);
466
                        }
467
                        writer.write(quoted);
468
                        if (++i < fieldsCount)
469
                            writer.write(',');
470
                    }
471
                    writer.write('\n');
472
                }
473
                row = readUntil(reader, sb, ROW_DELIM);
474
            }
475
        } finally {
476
            tmpFile.delete();
477
            if (writer != null)
478
                writer.close();
479
            else
480
                outs.close();
481
            reader.close();
17 ilm 482
        }
83 ilm 483
    }
17 ilm 484
 
83 ilm 485
    private String readUntil(BufferedReader reader, StringBuilder sb, String rowDelim) throws IOException {
486
        if (sb.capacity() == 0)
487
            return null;
488
        final int existing = sb.indexOf(rowDelim);
489
        if (existing >= 0) {
490
            final String res = sb.substring(0, existing);
491
            sb.delete(0, existing + rowDelim.length());
492
            return res;
17 ilm 493
        } else {
83 ilm 494
            final char[] buffer = new char[sb.capacity() / 3];
495
            final int readCount = reader.read(buffer);
496
            if (readCount <= 0) {
497
                final String res = sb.toString();
498
                sb.setLength(0);
499
                sb.trimToSize();
500
                assert sb.capacity() == 0;
501
                return res;
502
            } else {
503
                sb.append(buffer, 0, readCount);
504
                return readUntil(reader, sb, rowDelim);
505
            }
17 ilm 506
        }
507
    }
508
 
509
    @Override
510
    public boolean supportMultiAlterClause() {
511
        // support multiple if you omit the "add" : ALTER TABLE t add f1 int, f2 bit
512
        return false;
513
    }
514
 
515
    @Override
516
    public String getNullIsDataComparison(String x, boolean eq, String y) {
67 ilm 517
        final String nullSafe = x + " = " + y + " or ( " + x + " is null and " + y + " is null)";
518
        if (eq)
519
            return nullSafe;
520
        else
521
            return x + " <> " + y + " or (" + x + " is null and " + y + " is not null) " + " or (" + x + " is not null and " + y + " is null) ";
17 ilm 522
    }
523
 
524
    @Override
525
    public String getFunctionQuery(SQLBase b, Set<String> schemas) {
526
        return "  select name, schema_name(schema_id) as \"schema\", cast(OBJECT_DEFINITION(object_id) as varchar(4096)) as \"src\"\n"
142 ilm 527
                //
17 ilm 528
                + "  FROM " + new SQLName(b.getName(), "sys", "objects") + "\n"
529
                // scalar, inline table-valued, table-valued
142 ilm 530
                + "  where type IN ('FN', 'IF', 'TF') and SCHEMA_NAME( schema_id ) in (" + quoteStrings(schemas) + ") ";
17 ilm 531
    }
532
 
533
    @Override
67 ilm 534
    public String getTriggerQuery(SQLBase b, TablesMap tables) {
17 ilm 535
        // for some reason OBJECT_DEFINITION always returns null
536
        return "SELECT  trig.name as \"TRIGGER_NAME\", SCHEMA_NAME( tabl.schema_id ) as \"TABLE_SCHEMA\", tabl.name as \"TABLE_NAME\",  null as \"ACTION\", cast(OBJECT_DEFINITION(trig.object_id) as varchar(4096)) as \"SQL\"\n"
537
                //
538
                + "FROM " + new SQLName(b.getName(), "sys", "triggers") + " trig\n"
539
                //
540
                + "join " + new SQLName(b.getName(), "sys", "objects") + " tabl on trig.parent_id = tabl.object_id\n"
67 ilm 541
                // requested tables
142 ilm 542
                + getTablesMapJoin(tables, "SCHEMA_NAME( tabl.schema_id )", "tabl.name");
17 ilm 543
    }
544
 
545
    @Override
546
    public String getDropTrigger(Trigger t) {
73 ilm 547
        return "DROP TRIGGER " + new SQLName(t.getTable().getSchema().getName(), t.getName()).quote();
17 ilm 548
    }
549
 
550
    @Override
67 ilm 551
    public String getColumnsQuery(SQLBase b, TablesMap tables) {
83 ilm 552
        return "SELECT TABLE_SCHEMA as \"" + INFO_SCHEMA_NAMES_KEYS.get(0) + "\", TABLE_NAME as \"" + INFO_SCHEMA_NAMES_KEYS.get(1) + "\", COLUMN_NAME as \"" + INFO_SCHEMA_NAMES_KEYS.get(2)
553
                + "\" , CHARACTER_SET_NAME as \"CHARACTER_SET_NAME\", COLLATION_NAME as \"COLLATION_NAME\" from INFORMATION_SCHEMA.COLUMNS\n" +
554
                // requested tables
142 ilm 555
                getTablesMapJoin(tables, "TABLE_SCHEMA", "TABLE_NAME");
17 ilm 556
    }
557
 
558
    @Override
67 ilm 559
    public List<Map<String, Object>> getConstraints(SQLBase b, TablesMap tables) throws SQLException {
142 ilm 560
        final String where = getTablesMapJoin(tables, "SCHEMA_NAME(t.schema_id)", "t.name");
83 ilm 561
        final String sel = "SELECT SCHEMA_NAME(t.schema_id) AS \"TABLE_SCHEMA\", t.name AS \"TABLE_NAME\", k.name AS \"CONSTRAINT_NAME\", case k.type when 'UQ' then 'UNIQUE' when 'PK' then 'PRIMARY KEY' end as \"CONSTRAINT_TYPE\", col_name(c.object_id, c.column_id) AS \"COLUMN_NAME\", c.key_ordinal AS \"ORDINAL_POSITION\", null AS [DEFINITION]\n"
17 ilm 562
                + "FROM sys.key_constraints k\n"
563
                //
564
                + "JOIN sys.index_columns c ON c.object_id = k.parent_object_id AND c.index_id = k.unique_index_id\n"
26 ilm 565
                //
142 ilm 566
                + "JOIN sys.tables t ON t.object_id = k.parent_object_id\n" + where + "\nUNION ALL\n"
17 ilm 567
                //
83 ilm 568
                + "SELECT SCHEMA_NAME(t.schema_id) AS \"TABLE_SCHEMA\", t.name AS \"TABLE_NAME\", k.name AS \"CONSTRAINT_NAME\", 'CHECK' as \"CONSTRAINT_TYPE\", col.name AS \"COLUMN_NAME\", 1 AS \"ORDINAL_POSITION\", k.[definition] AS [DEFINITION]\n"
17 ilm 569
                + "FROM sys.check_constraints k\n"
570
                //
571
                + "join sys.tables t on k.parent_object_id = t.object_id\n"
26 ilm 572
                //
17 ilm 573
                + "left join sys.columns col on k.parent_column_id = col.column_id and col.object_id = t.object_id\n"
574
                //
142 ilm 575
                + where + "\nUNION ALL\n"
83 ilm 576
                //
577
                + "SELECT SCHEMA_NAME(t.schema_id) AS [TABLE_SCHEMA], t.name AS [TABLE_NAME], k.name AS [CONSTRAINT_NAME], 'DEFAULT' as [CONSTRAINT_TYPE], col.name AS [COLUMN_NAME], 1 AS [ORDINAL_POSITION], k.[definition] AS [DEFINITION]\n"
578
                + "FROM sys.[default_constraints] k\n"
579
                //
580
                + "JOIN sys.tables t ON t.object_id = k.parent_object_id\n"
581
                //
582
                + "left join sys.columns col on k.parent_column_id = col.column_id and col.object_id = t.object_id\n"
583
                //
17 ilm 584
                + where;
585
        // don't cache since we don't listen on system tables
586
        @SuppressWarnings("unchecked")
587
        final List<Map<String, Object>> res = (List<Map<String, Object>>) b.getDBSystemRoot().getDataSource().execute(sel, new IResultSetHandler(SQLDataSource.MAP_LIST_HANDLER, false));
588
        SQLSyntaxMySQL.mergeColumnNames(res);
589
        return res;
590
    }
591
 
592
    @Override
144 ilm 593
    public DeferredClause getSetTableComment(String comment) {
17 ilm 594
        return null;
595
    }
596
 
597
    @Override
598
    public String getConcatOp() {
599
        return "+";
600
    }
26 ilm 601
 
602
    @Override
603
    public String getRegexpOp(boolean negation) {
604
        // MS needs either the CLR : http://msdn.microsoft.com/en-us/magazine/cc163473.aspx
605
        // or http://www.codeproject.com/KB/database/xp_pcre.aspx
606
        return null;
607
    }
67 ilm 608
 
609
    @Override
142 ilm 610
    public String getDayOfWeek(String sqlTS) {
611
        return "SELECT DATEPART(dw, " + sqlTS + ")";
612
    }
613
 
614
    @Override
615
    public String getMonth(String sqlTS) {
616
        return "SELECT DATEPART(month, " + sqlTS + ")";
617
    }
618
 
619
    @Override
67 ilm 620
    public String getFormatTimestamp(String sqlTS, boolean basic) {
142 ilm 621
        final String extended = "CONVERT(nvarchar(30), " + sqlTS + ", 126) + '000'";
67 ilm 622
        if (basic) {
623
            return "replace( replace( " + extended + ", '-', ''), ':' , '' )";
624
        } else {
625
            return extended;
626
        }
627
    }
142 ilm 628
 
629
    @Override
630
    public String getFormatTimestamp(String sqlTS, String nativeFormat) {
631
        return "FORMAT(" + sqlTS + ", " + nativeFormat + ")";
632
    }
633
 
634
    @Override
635
    public String quoteForTimestampFormat(String text) {
636
        return StringUtils.doubleQuote(text);
637
    }
182 ilm 638
 
639
    @Override
640
    public String getSessionIDExpression() {
641
        return "@@SPID";
642
    }
643
 
644
    @Override
645
    public String getSessionsQuery(final DBSystemRoot sysRoot, final boolean includeSelf) {
646
        final String allRows = "SELECT ps.spid as \"ID\", cmd as \"QUERY\", users.name as \"USER_NAME\" FROM sys.sysprocesses ps\n"
647
                //
648
                + "JOIN sys.sysdatabases db on db.dbid = ps.dbid\nJOIN sys.sysusers users on users.uid = ps.uid\\n"
649
                //
650
                + " WHERE db.name=" + quoteString(sysRoot.getName());
651
        if (includeSelf)
652
            return allRows;
653
        return allRows + " and ps.spid != " + this.getSessionIDExpression();
654
    }
655
 
656
    @Override
657
    public String getVersionFunction() {
658
        // if this doesn't work, try SERVERPROPERTY('ProductVersion')
659
        return "@@VERSION";
660
    }
17 ilm 661
}