Project

General

Profile

Session.java

Patched org.h2.Session.cleanTempTables - Adrian Lungu, 07/11/2020 09:09 AM

Download (66.2 KB)

 
1
/*
2
 * Copyright 2004-2019 H2 Group. Multiple-Licensed under the MPL 2.0,
3
 * and the EPL 1.0 (https://h2database.com/html/license.html).
4
 * Initial Developer: H2 Group
5
 */
6
package org.h2.engine;
7

    
8
import java.util.ArrayDeque;
9
import java.util.ArrayList;
10
import java.util.BitSet;
11
import java.util.Collections;
12
import java.util.HashMap;
13
import java.util.HashSet;
14
import java.util.Iterator;
15
import java.util.LinkedList;
16
import java.util.Map;
17
import java.util.Random;
18
import java.util.Set;
19
import java.util.WeakHashMap;
20
import java.util.concurrent.TimeUnit;
21
import java.util.concurrent.atomic.AtomicReference;
22
import org.h2.api.ErrorCode;
23
import org.h2.command.Command;
24
import org.h2.command.CommandInterface;
25
import org.h2.command.Parser;
26
import org.h2.command.Prepared;
27
import org.h2.command.ddl.Analyze;
28
import org.h2.command.dml.Query;
29
import org.h2.command.dml.SetTypes;
30
import org.h2.constraint.Constraint;
31
import org.h2.index.Index;
32
import org.h2.index.ViewIndex;
33
import org.h2.jdbc.JdbcConnection;
34
import org.h2.message.DbException;
35
import org.h2.message.Trace;
36
import org.h2.message.TraceSystem;
37
import org.h2.mvstore.MVMap;
38
import org.h2.mvstore.db.MVIndex;
39
import org.h2.mvstore.db.MVTable;
40
import org.h2.mvstore.db.MVTableEngine;
41
import org.h2.mvstore.tx.Transaction;
42
import org.h2.mvstore.tx.TransactionStore;
43
import org.h2.result.ResultInterface;
44
import org.h2.result.Row;
45
import org.h2.result.SortOrder;
46
import org.h2.schema.Schema;
47
import org.h2.schema.Sequence;
48
import org.h2.store.DataHandler;
49
import org.h2.store.InDoubtTransaction;
50
import org.h2.store.LobStorageFrontend;
51
import org.h2.table.SubQueryInfo;
52
import org.h2.table.Table;
53
import org.h2.table.TableFilter;
54
import org.h2.table.TableType;
55
import org.h2.util.ColumnNamerConfiguration;
56
import org.h2.util.CurrentTimestamp;
57
import org.h2.util.NetworkConnectionInfo;
58
import org.h2.util.SmallLRUCache;
59
import org.h2.util.Utils;
60
import org.h2.value.DataType;
61
import org.h2.value.Value;
62
import org.h2.value.ValueArray;
63
import org.h2.value.ValueLong;
64
import org.h2.value.ValueNull;
65
import org.h2.value.ValueString;
66
import org.h2.value.ValueTimestampTimeZone;
67
import org.h2.value.VersionedValue;
68

    
69
/**
70
 * A session represents an embedded database connection. When using the server
71
 * mode, this object resides on the server side and communicates with a
72
 * SessionRemote object on the client side.
73
 */
74
public class Session extends SessionWithState implements TransactionStore.RollbackListener, CastDataProvider {
75

    
76
    public enum State { INIT, RUNNING, BLOCKED, SLEEP, THROTTLED, SUSPENDED, CLOSED }
77

    
78
    /**
79
     * This special log position means that the log entry has been written.
80
     */
81
    public static final int LOG_WRITTEN = -1;
82

    
83
    /**
84
     * The prefix of generated identifiers. It may not have letters, because
85
     * they are case sensitive.
86
     */
87
    private static final String SYSTEM_IDENTIFIER_PREFIX = "_";
88
    private static int nextSerialId;
89

    
90
    private final int serialId = nextSerialId++;
91
    private final Database database;
92
    private final User user;
93
    private final int id;
94

    
95
    private NetworkConnectionInfo networkConnectionInfo;
96

    
97
    private final ArrayList<Table> locks = Utils.newSmallArrayList();
98
    private UndoLog undoLog;
99
    private boolean autoCommit = true;
100
    private Random random;
101
    private int lockTimeout;
102

    
103
    private WeakHashMap<Sequence, Value> currentValueFor;
104
    private Value lastIdentity = ValueLong.get(0);
105
    private Value lastScopeIdentity = ValueLong.get(0);
106
    private Value lastTriggerIdentity;
107

    
108
    private int firstUncommittedLog = Session.LOG_WRITTEN;
109
    private int firstUncommittedPos = Session.LOG_WRITTEN;
110
    private HashMap<String, Savepoint> savepoints;
111
    private LocalTempTablesManager localTempTablesManager;
112
    private HashMap<String, Index> localTempTableIndexes;
113
    private HashMap<String, Constraint> localTempTableConstraints;
114
    private long throttleNs;
115
    private long lastThrottle;
116
    private Command currentCommand;
117
    private boolean allowLiterals;
118
    private String currentSchemaName;
119
    private String[] schemaSearchPath;
120
    private Trace trace;
121
    private HashMap<String, Value> removeLobMap;
122
    private int systemIdentifier;
123
    private HashMap<String, Procedure> procedures;
124
    private boolean undoLogEnabled = true;
125
    private boolean redoLogBinary = true;
126
    private boolean autoCommitAtTransactionEnd;
127
    private String currentTransactionName;
128
    private volatile long cancelAtNs;
129
    private final long sessionStart = System.currentTimeMillis();
130
    private ValueTimestampTimeZone transactionStart;
131
    private ValueTimestampTimeZone currentCommandStart;
132
    private HashMap<String, Value> variables;
133
    private HashSet<ResultInterface> temporaryResults;
134
    private int queryTimeout;
135
    private boolean commitOrRollbackDisabled;
136
    private Table waitForLock;
137
    private Thread waitForLockThread;
138
    private int modificationId;
139
    private int objectId;
140
    private final int queryCacheSize;
141
    private SmallLRUCache<String, Command> queryCache;
142
    private long modificationMetaID = -1;
143
    private SubQueryInfo subQueryInfo;
144
    private ArrayDeque<String> viewNameStack;
145
    private int preparingQueryExpression;
146
    private volatile SmallLRUCache<Object, ViewIndex> viewIndexCache;
147
    private HashMap<Object, ViewIndex> subQueryIndexCache;
148
    private boolean joinBatchEnabled;
149
    private boolean forceJoinOrder;
150
    private boolean lazyQueryExecution;
151
    private ColumnNamerConfiguration columnNamerConfiguration;
152
    /**
153
     * Tables marked for ANALYZE after the current transaction is committed.
154
     * Prevents us calling ANALYZE repeatedly in large transactions.
155
     */
156
    private HashSet<Table> tablesToAnalyze;
157

    
158
    /**
159
     * Temporary LOBs from result sets. Those are kept for some time. The
160
     * problem is that transactions are committed before the result is returned,
161
     * and in some cases the next transaction is already started before the
162
     * result is read (for example when using the server mode, when accessing
163
     * metadata methods). We can't simply free those values up when starting the
164
     * next transaction, because they would be removed too early.
165
     */
166
    private LinkedList<TimeoutValue> temporaryResultLobs;
167

    
168
    /**
169
     * The temporary LOBs that need to be removed on commit.
170
     */
171
    private ArrayList<Value> temporaryLobs;
172

    
173
    private Transaction transaction;
174
    private final AtomicReference<State> state = new AtomicReference<>(State.INIT);
175
    private long startStatement = -1;
176

    
177
    /**
178
     * Isolation level. Used only with MVStore engine, with PageStore engine the
179
     * value of this field shouldn't be changed or used to get the real
180
     * isolation level.
181
     */
182
    private IsolationLevel isolationLevel = IsolationLevel.READ_COMMITTED;
183

    
184
    /**
185
     * The snapshot data modification id. If isolation level doesn't allow
186
     * non-repeatable reads the session uses a snapshot versions of data. After
187
     * commit or rollback these snapshots are discarded and cached results of
188
     * queries may became invalid. Commit and rollback allocate a new data
189
     * modification id and store it here to forbid usage of older results.
190
     */
191
    private long snapshotDataModificationId;
192

    
193
    /**
194
     * Set of database object ids to be released at the end of transaction
195
     */
196
    private BitSet idsToRelease;
197

    
198
    public Session(Database database, User user, int id) {
199
        this.database = database;
200
        this.queryTimeout = database.getSettings().maxQueryTimeout;
201
        this.queryCacheSize = database.getSettings().queryCacheSize;
202
        this.user = user;
203
        this.id = id;
204
        this.lockTimeout = database.getLockTimeout();
205
        // PageStore creates a system session before initialization of the main schema
206
        Schema mainSchema = database.getMainSchema();
207
        this.currentSchemaName = mainSchema != null ? mainSchema.getName()
208
                : database.sysIdentifier(Constants.SCHEMA_MAIN);
209
        this.columnNamerConfiguration = ColumnNamerConfiguration.getDefault();
210
    }
211

    
212
    public void setLazyQueryExecution(boolean lazyQueryExecution) {
213
        this.lazyQueryExecution = lazyQueryExecution;
214
    }
215

    
216
    public boolean isLazyQueryExecution() {
217
        return lazyQueryExecution;
218
    }
219

    
220
    public void setForceJoinOrder(boolean forceJoinOrder) {
221
        this.forceJoinOrder = forceJoinOrder;
222
    }
223

    
224
    public boolean isForceJoinOrder() {
225
        return forceJoinOrder;
226
    }
227

    
228
    public void setJoinBatchEnabled(boolean joinBatchEnabled) {
229
        this.joinBatchEnabled = joinBatchEnabled;
230
    }
231

    
232
    public boolean isJoinBatchEnabled() {
233
        return joinBatchEnabled;
234
    }
235

    
236
    /**
237
     * Create a new row for a table.
238
     *
239
     * @param data the values
240
     * @param memory whether the row is in memory
241
     * @return the created row
242
     */
243
    public Row createRow(Value[] data, int memory) {
244
        return database.createRow(data, memory);
245
    }
246

    
247
    /**
248
     * Add a subquery info on top of the subquery info stack.
249
     *
250
     * @param masks the mask
251
     * @param filters the filters
252
     * @param filter the filter index
253
     * @param sortOrder the sort order
254
     */
255
    public void pushSubQueryInfo(int[] masks, TableFilter[] filters, int filter,
256
            SortOrder sortOrder) {
257
        subQueryInfo = new SubQueryInfo(subQueryInfo, masks, filters, filter, sortOrder);
258
    }
259

    
260
    /**
261
     * Remove the current subquery info from the stack.
262
     */
263
    public void popSubQueryInfo() {
264
        subQueryInfo = subQueryInfo.getUpper();
265
    }
266

    
267
    public SubQueryInfo getSubQueryInfo() {
268
        return subQueryInfo;
269
    }
270

    
271
    /**
272
     * Stores name of currently parsed view in a stack so it can be determined
273
     * during {@code prepare()}.
274
     *
275
     * @param parsingView
276
     *            {@code true} to store one more name, {@code false} to remove it
277
     *            from stack
278
     * @param viewName
279
     *            name of the view
280
     */
281
    public void setParsingCreateView(boolean parsingView, String viewName) {
282
        if (viewNameStack == null) {
283
            viewNameStack = new ArrayDeque<>(3);
284
        }
285
        if (parsingView) {
286
            viewNameStack.push(viewName);
287
        } else {
288
            String name = viewNameStack.pop();
289
            assert viewName.equals(name);
290
        }
291
    }
292

    
293
    public String getParsingCreateViewName() {
294
        return viewNameStack != null ? viewNameStack.peek() : null;
295
    }
296

    
297
    public boolean isParsingCreateView() {
298
        return viewNameStack != null && !viewNameStack.isEmpty();
299
    }
300

    
301
    /**
302
     * Optimize a query. This will remember the subquery info, clear it, prepare
303
     * the query, and reset the subquery info.
304
     *
305
     * @param query the query to prepare
306
     */
307
    public void optimizeQueryExpression(Query query) {
308
        // we have to hide current subQueryInfo if we are going to optimize
309
        // query expression
310
        SubQueryInfo tmp = subQueryInfo;
311
        subQueryInfo = null;
312
        preparingQueryExpression++;
313
        try {
314
            query.prepare();
315
        } finally {
316
            subQueryInfo = tmp;
317
            preparingQueryExpression--;
318
        }
319
    }
320

    
321
    public boolean isPreparingQueryExpression() {
322
        assert preparingQueryExpression >= 0;
323
        return preparingQueryExpression != 0;
324
    }
325

    
326
    @Override
327
    public ArrayList<String> getClusterServers() {
328
        return new ArrayList<>();
329
    }
330

    
331
    public boolean setCommitOrRollbackDisabled(boolean x) {
332
        boolean old = commitOrRollbackDisabled;
333
        commitOrRollbackDisabled = x;
334
        return old;
335
    }
336

    
337
    private void initVariables() {
338
        if (variables == null) {
339
            variables = database.newStringMap();
340
        }
341
    }
342

    
343
    /**
344
     * Set the value of the given variable for this session.
345
     *
346
     * @param name the name of the variable (may not be null)
347
     * @param value the new value (may not be null)
348
     */
349
    public void setVariable(String name, Value value) {
350
        initVariables();
351
        modificationId++;
352
        Value old;
353
        if (value == ValueNull.INSTANCE) {
354
            old = variables.remove(name);
355
        } else {
356
            // link LOB values, to make sure we have our own object
357
            value = value.copy(database,
358
                    LobStorageFrontend.TABLE_ID_SESSION_VARIABLE);
359
            old = variables.put(name, value);
360
        }
361
        if (old != null) {
362
            // remove the old value (in case it is a lob)
363
            old.remove();
364
        }
365
    }
366

    
367
    /**
368
     * Get the value of the specified user defined variable. This method always
369
     * returns a value; it returns ValueNull.INSTANCE if the variable doesn't
370
     * exist.
371
     *
372
     * @param name the variable name
373
     * @return the value, or NULL
374
     */
375
    public Value getVariable(String name) {
376
        initVariables();
377
        Value v = variables.get(name);
378
        return v == null ? ValueNull.INSTANCE : v;
379
    }
380

    
381
    /**
382
     * Get the list of variable names that are set for this session.
383
     *
384
     * @return the list of names
385
     */
386
    public String[] getVariableNames() {
387
        if (variables == null) {
388
            return new String[0];
389
        }
390
        return variables.keySet().toArray(new String[variables.size()]);
391
    }
392

    
393
    /**
394
     * Get the local temporary table if one exists with that name, or null if
395
     * not.
396
     *
397
     * @param name the table name
398
     * @return the table, or null
399
     */
400
    public Table findLocalTempTable(String name) {
401
        if (localTempTablesManager == null) {
402
            return null;
403
        }
404
        return localTempTablesManager.get(name);
405
    }
406

    
407
    public ArrayList<Table> getLocalTempTables() {
408
        if (localTempTablesManager == null) {
409
            return Utils.newSmallArrayList();
410
        }
411
        return localTempTablesManager.getAll();
412
    }
413

    
414
    /**
415
     * Add a local temporary table to this session.
416
     *
417
     * @param table the table to add
418
     * @throws DbException if a table with this name already exists
419
     */
420
    public void addLocalTempTable(Table table) {
421
        if (localTempTablesManager == null) {
422
            localTempTablesManager = new LocalTempTablesManager(database);
423
        }
424
        if (localTempTablesManager.has(table.getName())) {
425
            StringBuilder builder = new StringBuilder();
426
            table.getSQL(builder, false).append(" AS ");
427
            Parser.quoteIdentifier(table.getName(), false);
428
            throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, builder.toString());
429
        }
430
        modificationId++;
431
        localTempTablesManager.register(table);
432
    }
433

    
434
    /**
435
     * Drop and remove the given local temporary table from this session.
436
     *
437
     * @param table the table
438
     */
439
    public void removeLocalTempTable(Table table) {
440
        // Exception thrown in org.h2.engine.Database.removeMeta if line below
441
        // is missing with TestGeneralCommonTableQueries
442
        boolean wasLocked = database.lockMeta(this);
443
        try {
444
            modificationId++;
445
            if (localTempTablesManager != null) {
446
                localTempTablesManager.remove(table.getName());
447
            }
448
            synchronized (database) {
449
                table.removeChildrenAndResources(this);
450
            }
451
        } finally {
452
            if (!wasLocked) {
453
                database.unlockMeta(this);
454
            }
455
        }
456
    }
457

    
458
    /**
459
     * Get the local temporary index if one exists with that name, or null if
460
     * not.
461
     *
462
     * @param name the table name
463
     * @return the table, or null
464
     */
465
    public Index findLocalTempTableIndex(String name) {
466
        if (localTempTableIndexes == null) {
467
            return null;
468
        }
469
        return localTempTableIndexes.get(name);
470
    }
471

    
472
    public HashMap<String, Index> getLocalTempTableIndexes() {
473
        if (localTempTableIndexes == null) {
474
            return new HashMap<>();
475
        }
476
        return localTempTableIndexes;
477
    }
478

    
479
    /**
480
     * Add a local temporary index to this session.
481
     *
482
     * @param index the index to add
483
     * @throws DbException if a index with this name already exists
484
     */
485
    public void addLocalTempTableIndex(Index index) {
486
        if (localTempTableIndexes == null) {
487
            localTempTableIndexes = database.newStringMap();
488
        }
489
        if (localTempTableIndexes.get(index.getName()) != null) {
490
            throw DbException.get(ErrorCode.INDEX_ALREADY_EXISTS_1, index.getSQL(false));
491
        }
492
        localTempTableIndexes.put(index.getName(), index);
493
    }
494

    
495
    /**
496
     * Drop and remove the given local temporary index from this session.
497
     *
498
     * @param index the index
499
     */
500
    public void removeLocalTempTableIndex(Index index) {
501
        if (localTempTableIndexes != null) {
502
            localTempTableIndexes.remove(index.getName());
503
            synchronized (database) {
504
                index.removeChildrenAndResources(this);
505
            }
506
        }
507
    }
508

    
509
    /**
510
     * Get the local temporary constraint if one exists with that name, or
511
     * null if not.
512
     *
513
     * @param name the constraint name
514
     * @return the constraint, or null
515
     */
516
    public Constraint findLocalTempTableConstraint(String name) {
517
        if (localTempTableConstraints == null) {
518
            return null;
519
        }
520
        return localTempTableConstraints.get(name);
521
    }
522

    
523
    /**
524
     * Get the map of constraints for all constraints on local, temporary
525
     * tables, if any. The map's keys are the constraints' names.
526
     *
527
     * @return the map of constraints, or null
528
     */
529
    public HashMap<String, Constraint> getLocalTempTableConstraints() {
530
        if (localTempTableConstraints == null) {
531
            return new HashMap<>();
532
        }
533
        return localTempTableConstraints;
534
    }
535

    
536
    /**
537
     * Add a local temporary constraint to this session.
538
     *
539
     * @param constraint the constraint to add
540
     * @throws DbException if a constraint with the same name already exists
541
     */
542
    public void addLocalTempTableConstraint(Constraint constraint) {
543
        if (localTempTableConstraints == null) {
544
            localTempTableConstraints = database.newStringMap();
545
        }
546
        String name = constraint.getName();
547
        if (localTempTableConstraints.get(name) != null) {
548
            throw DbException.get(ErrorCode.CONSTRAINT_ALREADY_EXISTS_1, constraint.getSQL(false));
549
        }
550
        localTempTableConstraints.put(name, constraint);
551
    }
552

    
553
    /**
554
     * Drop and remove the given local temporary constraint from this session.
555
     *
556
     * @param constraint the constraint
557
     */
558
    void removeLocalTempTableConstraint(Constraint constraint) {
559
        if (localTempTableConstraints != null) {
560
            localTempTableConstraints.remove(constraint.getName());
561
            synchronized (database) {
562
                constraint.removeChildrenAndResources(this);
563
            }
564
        }
565
    }
566

    
567
    @Override
568
    public boolean getAutoCommit() {
569
        return autoCommit;
570
    }
571

    
572
    public User getUser() {
573
        return user;
574
    }
575

    
576
    @Override
577
    public void setAutoCommit(boolean b) {
578
        autoCommit = b;
579
    }
580

    
581
    public int getLockTimeout() {
582
        return lockTimeout;
583
    }
584

    
585
    public void setLockTimeout(int lockTimeout) {
586
        this.lockTimeout = lockTimeout;
587
        if (transaction != null) {
588
            transaction.setTimeoutMillis(lockTimeout);
589
        }
590
    }
591

    
592
    @Override
593
    public synchronized CommandInterface prepareCommand(String sql,
594
            int fetchSize) {
595
        return prepareLocal(sql);
596
    }
597

    
598
    /**
599
     * Parse and prepare the given SQL statement. This method also checks the
600
     * rights.
601
     *
602
     * @param sql the SQL statement
603
     * @return the prepared statement
604
     */
605
    public Prepared prepare(String sql) {
606
        return prepare(sql, false, false);
607
    }
608

    
609
    /**
610
     * Parse and prepare the given SQL statement.
611
     *
612
     * @param sql the SQL statement
613
     * @param rightsChecked true if the rights have already been checked
614
     * @param literalsChecked true if the sql string has already been checked
615
     *            for literals (only used if ALLOW_LITERALS NONE is set).
616
     * @return the prepared statement
617
     */
618
    public Prepared prepare(String sql, boolean rightsChecked, boolean literalsChecked) {
619
        Parser parser = new Parser(this);
620
        parser.setRightsChecked(rightsChecked);
621
        parser.setLiteralsChecked(literalsChecked);
622
        return parser.prepare(sql);
623
    }
624

    
625
    /**
626
     * Parse and prepare the given SQL statement.
627
     * This method also checks if the connection has been closed.
628
     *
629
     * @param sql the SQL statement
630
     * @return the prepared statement
631
     */
632
    public Command prepareLocal(String sql) {
633
        if (isClosed()) {
634
            throw DbException.get(ErrorCode.CONNECTION_BROKEN_1,
635
                    "session closed");
636
        }
637
        Command command;
638
        if (queryCacheSize > 0) {
639
            if (queryCache == null) {
640
                queryCache = SmallLRUCache.newInstance(queryCacheSize);
641
                modificationMetaID = database.getModificationMetaId();
642
            } else {
643
                long newModificationMetaID = database.getModificationMetaId();
644
                if (newModificationMetaID != modificationMetaID) {
645
                    queryCache.clear();
646
                    modificationMetaID = newModificationMetaID;
647
                }
648
                command = queryCache.get(sql);
649
                if (command != null && command.canReuse()) {
650
                    command.reuse();
651
                    return command;
652
                }
653
            }
654
        }
655
        Parser parser = new Parser(this);
656
        try {
657
            command = parser.prepareCommand(sql);
658
        } finally {
659
            // we can't reuse sub-query indexes, so just drop the whole cache
660
            subQueryIndexCache = null;
661
        }
662
        command.prepareJoinBatch();
663
        if (queryCache != null) {
664
            if (command.isCacheable()) {
665
                queryCache.put(sql, command);
666
            }
667
        }
668
        return command;
669
    }
670

    
671
    /**
672
     * Arranges for the specified database object id to be released
673
     * at the end of the current transaction.
674
     * @param id to be scheduled
675
     */
676
    void scheduleDatabaseObjectIdForRelease(int id) {
677
        if (idsToRelease == null) {
678
            idsToRelease = new BitSet();
679
        }
680
        idsToRelease.set(id);
681
    }
682

    
683
    public Database getDatabase() {
684
        return database;
685
    }
686

    
687
    @Override
688
    public int getPowerOffCount() {
689
        return database.getPowerOffCount();
690
    }
691

    
692
    @Override
693
    public void setPowerOffCount(int count) {
694
        database.setPowerOffCount(count);
695
    }
696

    
697
    /**
698
     * Commit the current transaction. If the statement was not a data
699
     * definition statement, and if there are temporary tables that should be
700
     * dropped or truncated at commit, this is done as well.
701
     *
702
     * @param ddl if the statement was a data definition statement
703
     */
704
    public void commit(boolean ddl) {
705
        checkCommitRollback();
706

    
707
        currentTransactionName = null;
708
        transactionStart = null;
709
        boolean forRepeatableRead = false;
710
        if (transaction != null) {
711
            forRepeatableRead = !isolationLevel.allowNonRepeatableRead();
712
            try {
713
                markUsedTablesAsUpdated();
714
                transaction.commit();
715
            } finally {
716
                transaction = null;
717
            }
718
        } else if (containsUncommitted()) {
719
            // need to commit even if rollback is not possible
720
            // (create/drop table and so on)
721
            database.commit(this);
722
        }
723
        removeTemporaryLobs(true);
724
        if (undoLog != null && undoLog.size() > 0) {
725
            undoLog.clear();
726
        }
727
        if (!ddl) {
728
            // do not clean the temp tables if the last command was a
729
            // create/drop
730
            cleanTempTables(false);
731
            if (autoCommitAtTransactionEnd) {
732
                autoCommit = true;
733
                autoCommitAtTransactionEnd = false;
734
            }
735
        }
736

    
737
        if (tablesToAnalyze != null) {
738
            analyzeTables();
739
            if (database.isMVStore()) {
740
                // table analysis opens a new transaction(s),
741
                // so we need to commit afterwards whatever leftovers might be
742
                commit(true);
743
            }
744
        }
745
        endTransaction(forRepeatableRead);
746
    }
747

    
748
    private void markUsedTablesAsUpdated() {
749
        // TODO should not rely on locking
750
        if (!locks.isEmpty()) {
751
            for (Table t : locks) {
752
                if (t instanceof MVTable) {
753
                    ((MVTable) t).commit();
754
                }
755
            }
756
        }
757
    }
758

    
759
    private void analyzeTables() {
760
        int rowCount = getDatabase().getSettings().analyzeSample / 10;
761
        for (Table table : tablesToAnalyze) {
762
            Analyze.analyzeTable(this, table, rowCount, false);
763
        }
764
        // analyze can lock the meta
765
        database.unlockMeta(this);
766
        tablesToAnalyze = null;
767
    }
768

    
769
    private void removeTemporaryLobs(boolean onTimeout) {
770
        assert this != getDatabase().getLobSession() || Thread.holdsLock(this) || Thread.holdsLock(getDatabase());
771
        if (temporaryLobs != null) {
772
            for (Value v : temporaryLobs) {
773
                if (!v.isLinkedToTable()) {
774
                    v.remove();
775
                }
776
            }
777
            temporaryLobs.clear();
778
        }
779
        if (temporaryResultLobs != null && !temporaryResultLobs.isEmpty()) {
780
            long keepYoungerThan = System.nanoTime() -
781
                    TimeUnit.MILLISECONDS.toNanos(database.getSettings().lobTimeout);
782
            while (!temporaryResultLobs.isEmpty()) {
783
                TimeoutValue tv = temporaryResultLobs.getFirst();
784
                if (onTimeout && tv.created >= keepYoungerThan) {
785
                    break;
786
                }
787
                Value v = temporaryResultLobs.removeFirst().value;
788
                if (!v.isLinkedToTable()) {
789
                    v.remove();
790
                }
791
            }
792
        }
793
    }
794

    
795
    private void checkCommitRollback() {
796
        if (commitOrRollbackDisabled && !locks.isEmpty()) {
797
            throw DbException.get(ErrorCode.COMMIT_ROLLBACK_NOT_ALLOWED);
798
        }
799
    }
800

    
801
    private void endTransaction(boolean forRepeatableRead) {
802
        if (removeLobMap != null && removeLobMap.size() > 0) {
803
            if (database.getStore() == null) {
804
                // need to flush the transaction log, because we can't unlink
805
                // lobs if the commit record is not written
806
                database.flush();
807
            }
808
            for (Value v : removeLobMap.values()) {
809
                v.remove();
810
            }
811
            removeLobMap = null;
812
        }
813
        unlockAll();
814
        if (idsToRelease != null) {
815
            database.releaseDatabaseObjectIds(idsToRelease);
816
            idsToRelease = null;
817
        }
818
        if (forRepeatableRead) {
819
            snapshotDataModificationId = database.getNextModificationDataId();
820
        }
821
    }
822

    
823
    /**
824
     * Returns the data modification id of transaction's snapshot, or 0 if
825
     * isolation level doesn't use snapshots.
826
     *
827
     * @return the data modification id of transaction's snapshot, or 0
828
     */
829
    public long getSnapshotDataModificationId() {
830
        return snapshotDataModificationId;
831
    }
832

    
833
    /**
834
     * Fully roll back the current transaction.
835
     */
836
    public void rollback() {
837
        checkCommitRollback();
838
        currentTransactionName = null;
839
        transactionStart = null;
840
        boolean needCommit = undoLog != null && undoLog.size() > 0 || transaction != null;
841
        boolean forRepeatableRead = transaction != null && !isolationLevel.allowNonRepeatableRead();
842
        if (needCommit) {
843
            rollbackTo(null);
844
        }
845
        if (!locks.isEmpty() || needCommit) {
846
            database.commit(this);
847
        }
848
        idsToRelease = null;
849
        cleanTempTables(false);
850
        if (autoCommitAtTransactionEnd) {
851
            autoCommit = true;
852
            autoCommitAtTransactionEnd = false;
853
        }
854
        endTransaction(forRepeatableRead);
855
    }
856

    
857
    /**
858
     * Partially roll back the current transaction.
859
     *
860
     * @param savepoint the savepoint to which should be rolled back
861
     */
862
    public void rollbackTo(Savepoint savepoint) {
863
        int index = savepoint == null ? 0 : savepoint.logIndex;
864
        if (undoLog != null) {
865
            while (undoLog.size() > index) {
866
                UndoLogRecord entry = undoLog.getLast();
867
                entry.undo(this);
868
                undoLog.removeLast();
869
            }
870
        }
871
        if (transaction != null) {
872
            markUsedTablesAsUpdated();
873
            if (savepoint == null) {
874
                transaction.rollback();
875
                transaction = null;
876
            } else {
877
                transaction.rollbackToSavepoint(savepoint.transactionSavepoint);
878
            }
879
        }
880
        if (savepoints != null) {
881
            String[] names = savepoints.keySet().toArray(new String[savepoints.size()]);
882
            for (String name : names) {
883
                Savepoint sp = savepoints.get(name);
884
                int savepointIndex = sp.logIndex;
885
                if (savepointIndex > index) {
886
                    savepoints.remove(name);
887
                }
888
            }
889
        }
890

    
891
        // Because cache may have captured query result (in Query.lastResult),
892
        // which is based on data from uncommitted transaction.,
893
        // It is not valid after rollback, therefore cache has to be cleared.
894
        if (queryCache != null) {
895
            queryCache.clear();
896
        }
897
    }
898

    
899
    @Override
900
    public boolean hasPendingTransaction() {
901
        return undoLog != null && undoLog.size() > 0;
902
    }
903

    
904
    /**
905
     * Create a savepoint to allow rolling back to this state.
906
     *
907
     * @return the savepoint
908
     */
909
    public Savepoint setSavepoint() {
910
        Savepoint sp = new Savepoint();
911
        if (undoLog != null) {
912
            sp.logIndex = undoLog.size();
913
        }
914
        if (database.getStore() != null) {
915
            sp.transactionSavepoint = getStatementSavepoint();
916
        }
917
        return sp;
918
    }
919

    
920
    public int getId() {
921
        return id;
922
    }
923

    
924
    @Override
925
    public void cancel() {
926
        cancelAtNs = System.nanoTime();
927
    }
928

    
929
    /**
930
     * Cancel the transaction and close the session if needed.
931
     */
932
    void suspend() {
933
        cancel();
934
        if (transitionToState(State.SUSPENDED, false) == State.SLEEP) {
935
            close();
936
        }
937
    }
938

    
939
    @Override
940
    public void close() {
941
        // this is the only operation that can be invoked concurrently
942
        // so, we should prevent double-closure
943
        if (state.getAndSet(State.CLOSED) != State.CLOSED) {
944
            try {
945
                database.throwLastBackgroundException();
946

    
947
                database.checkPowerOff();
948

    
949
                // release any open table locks
950
                rollback();
951

    
952
                removeTemporaryLobs(false);
953
                cleanTempTables(true);
954
                commit(true);       // temp table removal may have opened new transaction
955
                if (undoLog != null) {
956
                    undoLog.clear();
957
                }
958
                // Table#removeChildrenAndResources can take the meta lock,
959
                // and we need to unlock before we call removeSession(), which might
960
                // want to take the meta lock using the system session.
961
                database.unlockMeta(this);
962
            } finally {
963
                database.removeSession(this);
964
            }
965
        }
966
    }
967

    
968
    /**
969
     * Register table as updated within current transaction.
970
     * Table is unlocked on commit or rollback.
971
     * It also assumes that table will be modified by transaction.
972
     *
973
     * @param table the table that is locked
974
     */
975
    public void registerTableAsLocked(Table table) {
976
        if (SysProperties.CHECK) {
977
            if (locks.contains(table)) {
978
                DbException.throwInternalError(table.toString());
979
            }
980
        }
981
        locks.add(table);
982
    }
983

    
984
    /**
985
     * Register table as updated within current transaction.
986
     * This is used instead of table locking when lock mode is LOCK_MODE_OFF.
987
     *
988
     * @param table to register
989
     */
990
    public void registerTableAsUpdated(Table table) {
991
        if (!locks.contains(table)) {
992
            locks.add(table);
993
        }
994
    }
995

    
996
    /**
997
     * Add an undo log entry to this session.
998
     *
999
     * @param table the table
1000
     * @param operation the operation type (see {@link UndoLogRecord})
1001
     * @param row the row
1002
     */
1003
    public void log(Table table, short operation, Row row) {
1004
        if (table.isMVStore()) {
1005
            return;
1006
        }
1007
        if (undoLogEnabled) {
1008
            UndoLogRecord log = new UndoLogRecord(table, operation, row);
1009
            // called _after_ the row was inserted successfully into the table,
1010
            // otherwise rollback will try to rollback a not-inserted row
1011
            if (SysProperties.CHECK) {
1012
                int lockMode = database.getLockMode();
1013
                if (lockMode != Constants.LOCK_MODE_OFF &&
1014
                        !database.isMVStore()) {
1015
                    TableType tableType = log.getTable().getTableType();
1016
                    if (!locks.contains(log.getTable())
1017
                            && TableType.TABLE_LINK != tableType
1018
                            && TableType.EXTERNAL_TABLE_ENGINE != tableType) {
1019
                        DbException.throwInternalError(String.valueOf(tableType));
1020
                    }
1021
                }
1022
            }
1023
            if (undoLog == null) {
1024
                undoLog = new UndoLog(database);
1025
            }
1026
            undoLog.add(log);
1027
        }
1028
    }
1029

    
1030
    /**
1031
     * Unlock just this table.
1032
     *
1033
     * @param t the table to unlock
1034
     */
1035
    void unlock(Table t) {
1036
        locks.remove(t);
1037
    }
1038

    
1039
    private void unlockAll() {
1040
        if (undoLog != null && undoLog.size() > 0) {
1041
            DbException.throwInternalError();
1042
        }
1043
        if (!locks.isEmpty()) {
1044
            Table[] array = locks.toArray(new Table[0]);
1045
            for (Table t : array) {
1046
                if (t != null) {
1047
                    t.unlock(this);
1048
                }
1049
            }
1050
            locks.clear();
1051
        }
1052
        database.unlockMetaDebug(this);
1053
        savepoints = null;
1054
        sessionStateChanged = true;
1055
    }
1056

    
1057
    private void cleanTempTables(boolean closeSession) {
1058
        if (localTempTablesManager != null && localTempTablesManager.canClean(closeSession)) {
1059
            if (database.isMVStore()) {
1060
                _cleanTempTables(closeSession);
1061
            } else {
1062
                synchronized (database) {
1063
                    _cleanTempTables(closeSession);
1064
                }
1065
            }
1066
        }
1067
    }
1068

    
1069
    private void _cleanTempTables(boolean closeSession) {
1070
        HashMap<String, Table> localTempTables = localTempTablesManager.getLocalTempTables();     
1071
        if (closeSession && localTempTables != null) {
1072
            Iterator<Table> it = localTempTables.values().iterator();
1073
            while (it.hasNext()) {
1074
                Table table = it.next();
1075
                modificationId++;
1076
                table.setModified();
1077
                it.remove();
1078
                localTempTablesManager.remove(table.getName());
1079
                // Exception thrown in org.h2.engine.Database.removeMeta
1080
                // if line below is missing with TestDeadlock
1081
                database.lockMeta(this);
1082
                table.removeChildrenAndResources(this);
1083
                // need to commit, otherwise recovery might
1084
                // ignore the table removal
1085
                database.commit(this);
1086
            }
1087
            return;
1088
        }
1089
        
1090
        HashMap<String, Table> onCommitDropTables = localTempTablesManager.getOnCommitDropTables();
1091
        if (onCommitDropTables != null) {
1092
            Iterator<Table> it = onCommitDropTables.values().iterator();
1093
            while (it.hasNext()) {
1094
                Table table = it.next();
1095
                modificationId++;
1096
                table.setModified();
1097
                it.remove();
1098
                localTempTablesManager.remove(table.getName());
1099
                // Exception thrown in org.h2.engine.Database.removeMeta
1100
                // if line below is missing with TestDeadlock
1101
                database.lockMeta(this);
1102
                table.removeChildrenAndResources(this);
1103
            }
1104
        }
1105
        
1106
        HashMap<String, Table> onCommitTruncateTables = localTempTablesManager.getOnCommitTruncateTables();
1107
        if (onCommitTruncateTables != null) {
1108
            Iterator<Table> it = onCommitTruncateTables.values().iterator();
1109
            while (it.hasNext()) {
1110
                Table table = it.next();
1111
                table.truncate(this);
1112
            }
1113
        }
1114
    }
1115

    
1116
    public Random getRandom() {
1117
        if (random == null) {
1118
            random = new Random();
1119
        }
1120
        return random;
1121
    }
1122

    
1123
    @Override
1124
    public Trace getTrace() {
1125
        if (trace != null && !isClosed()) {
1126
            return trace;
1127
        }
1128
        String traceModuleName = "jdbc[" + id + "]";
1129
        if (isClosed()) {
1130
            return new TraceSystem(null).getTrace(traceModuleName);
1131
        }
1132
        trace = database.getTraceSystem().getTrace(traceModuleName);
1133
        return trace;
1134
    }
1135

    
1136
    /**
1137
     * Sets the current value of the sequence and last identity value for this
1138
     * session.
1139
     *
1140
     * @param sequence
1141
     *            the sequence
1142
     * @param value
1143
     *            the current value of the sequence
1144
     */
1145
    public void setCurrentValueFor(Sequence sequence, Value value) {
1146
        WeakHashMap<Sequence, Value> currentValueFor = this.currentValueFor;
1147
        if (currentValueFor == null) {
1148
            this.currentValueFor = currentValueFor = new WeakHashMap<>();
1149
        }
1150
        currentValueFor.put(sequence, value);
1151
        setLastIdentity(value);
1152
    }
1153

    
1154
    /**
1155
     * Returns the current value of the sequence in this session.
1156
     *
1157
     * @param sequence
1158
     *            the sequence
1159
     * @return the current value of the sequence in this session
1160
     * @throws DbException
1161
     *             if current value is not defined
1162
     */
1163
    public Value getCurrentValueFor(Sequence sequence) {
1164
        WeakHashMap<Sequence, Value> currentValueFor = this.currentValueFor;
1165
        if (currentValueFor != null) {
1166
            Value value = currentValueFor.get(sequence);
1167
            if (value != null) {
1168
                return value;
1169
            }
1170
        }
1171
        throw DbException.get(ErrorCode.CURRENT_SEQUENCE_VALUE_IS_NOT_DEFINED_IN_SESSION_1, sequence.getSQL(false));
1172
    }
1173

    
1174
    public void setLastIdentity(Value last) {
1175
        this.lastIdentity = last;
1176
        this.lastScopeIdentity = last;
1177
    }
1178

    
1179
    public Value getLastIdentity() {
1180
        return lastIdentity;
1181
    }
1182

    
1183
    public void setLastScopeIdentity(Value last) {
1184
        this.lastScopeIdentity = last;
1185
    }
1186

    
1187
    public Value getLastScopeIdentity() {
1188
        return lastScopeIdentity;
1189
    }
1190

    
1191
    public void setLastTriggerIdentity(Value last) {
1192
        this.lastTriggerIdentity = last;
1193
    }
1194

    
1195
    public Value getLastTriggerIdentity() {
1196
        return lastTriggerIdentity;
1197
    }
1198

    
1199
    /**
1200
     * Called when a log entry for this session is added. The session keeps
1201
     * track of the first entry in the transaction log that is not yet
1202
     * committed.
1203
     *
1204
     * @param logId the transaction log id
1205
     * @param pos the position of the log entry in the transaction log
1206
     */
1207
    public void addLogPos(int logId, int pos) {
1208
        if (firstUncommittedLog == Session.LOG_WRITTEN) {
1209
            firstUncommittedLog = logId;
1210
            firstUncommittedPos = pos;
1211
        }
1212
    }
1213

    
1214
    public int getFirstUncommittedLog() {
1215
        return firstUncommittedLog;
1216
    }
1217

    
1218
    /**
1219
     * This method is called after the transaction log has written the commit
1220
     * entry for this session.
1221
     */
1222
    void setAllCommitted() {
1223
        firstUncommittedLog = Session.LOG_WRITTEN;
1224
        firstUncommittedPos = Session.LOG_WRITTEN;
1225
    }
1226

    
1227
    /**
1228
     * Whether the session contains any uncommitted changes.
1229
     *
1230
     * @return true if yes
1231
     */
1232
    public boolean containsUncommitted() {
1233
        if (database.getStore() != null) {
1234
            return transaction != null && transaction.hasChanges();
1235
        }
1236
        return firstUncommittedLog != Session.LOG_WRITTEN;
1237
    }
1238

    
1239
    /**
1240
     * Create a savepoint that is linked to the current log position.
1241
     *
1242
     * @param name the savepoint name
1243
     */
1244
    public void addSavepoint(String name) {
1245
        if (savepoints == null) {
1246
            savepoints = database.newStringMap();
1247
        }
1248
        savepoints.put(name, setSavepoint());
1249
    }
1250

    
1251
    /**
1252
     * Undo all operations back to the log position of the given savepoint.
1253
     *
1254
     * @param name the savepoint name
1255
     */
1256
    public void rollbackToSavepoint(String name) {
1257
        checkCommitRollback();
1258
        currentTransactionName = null;
1259
        transactionStart = null;
1260
        if (savepoints == null) {
1261
            throw DbException.get(ErrorCode.SAVEPOINT_IS_INVALID_1, name);
1262
        }
1263
        Savepoint savepoint = savepoints.get(name);
1264
        if (savepoint == null) {
1265
            throw DbException.get(ErrorCode.SAVEPOINT_IS_INVALID_1, name);
1266
        }
1267
        rollbackTo(savepoint);
1268
    }
1269

    
1270
    /**
1271
     * Prepare the given transaction.
1272
     *
1273
     * @param transactionName the name of the transaction
1274
     */
1275
    public void prepareCommit(String transactionName) {
1276
        if (containsUncommitted()) {
1277
            // need to commit even if rollback is not possible (create/drop
1278
            // table and so on)
1279
            database.prepareCommit(this, transactionName);
1280
        }
1281
        currentTransactionName = transactionName;
1282
    }
1283

    
1284
    /**
1285
     * Commit or roll back the given transaction.
1286
     *
1287
     * @param transactionName the name of the transaction
1288
     * @param commit true for commit, false for rollback
1289
     */
1290
    public void setPreparedTransaction(String transactionName, boolean commit) {
1291
        if (currentTransactionName != null &&
1292
                currentTransactionName.equals(transactionName)) {
1293
            if (commit) {
1294
                commit(false);
1295
            } else {
1296
                rollback();
1297
            }
1298
        } else {
1299
            ArrayList<InDoubtTransaction> list = database
1300
                    .getInDoubtTransactions();
1301
            int state = commit ? InDoubtTransaction.COMMIT
1302
                    : InDoubtTransaction.ROLLBACK;
1303
            boolean found = false;
1304
            if (list != null) {
1305
                for (InDoubtTransaction p: list) {
1306
                    if (p.getTransactionName().equals(transactionName)) {
1307
                        p.setState(state);
1308
                        found = true;
1309
                        break;
1310
                    }
1311
                }
1312
            }
1313
            if (!found) {
1314
                throw DbException.get(ErrorCode.TRANSACTION_NOT_FOUND_1,
1315
                        transactionName);
1316
            }
1317
        }
1318
    }
1319

    
1320
    @Override
1321
    public boolean isClosed() {
1322
        return state.get() == State.CLOSED;
1323
    }
1324

    
1325
    public boolean isOpen() {
1326
        State current = state.get();
1327
        checkSuspended(current);
1328
        return current != State.CLOSED;
1329
    }
1330

    
1331
    public void setThrottle(int throttle) {
1332
        this.throttleNs = TimeUnit.MILLISECONDS.toNanos(throttle);
1333
    }
1334

    
1335
    /**
1336
     * Wait for some time if this session is throttled (slowed down).
1337
     */
1338
    public void throttle() {
1339
        if (currentCommandStart == null) {
1340
            currentCommandStart = CurrentTimestamp.get();
1341
        }
1342
        if (throttleNs == 0) {
1343
            return;
1344
        }
1345
        long time = System.nanoTime();
1346
        if (lastThrottle + TimeUnit.MILLISECONDS.toNanos(Constants.THROTTLE_DELAY) > time) {
1347
            return;
1348
        }
1349
        lastThrottle = time + throttleNs;
1350
        State prevState = transitionToState(State.THROTTLED, false);
1351
        try {
1352
            Thread.sleep(TimeUnit.NANOSECONDS.toMillis(throttleNs));
1353
        } catch (InterruptedException ignore) {
1354
        } finally {
1355
            transitionToState(prevState, false);
1356
        }
1357
    }
1358

    
1359
    /**
1360
     * Set the current command of this session. This is done just before
1361
     * executing the statement.
1362
     *
1363
     * @param command the command
1364
     */
1365
    private void setCurrentCommand(Command command) {
1366
        State targetState = command == null ? State.SLEEP : State.RUNNING;
1367
        transitionToState(targetState, true);
1368
        if (isOpen()) {
1369
            currentCommand = command;
1370
            if (command != null) {
1371
                if (queryTimeout > 0) {
1372
                    currentCommandStart = CurrentTimestamp.get();
1373
                    long now = System.nanoTime();
1374
                    cancelAtNs = now + TimeUnit.MILLISECONDS.toNanos(queryTimeout);
1375
                } else {
1376
                    currentCommandStart = null;
1377
                }
1378
            }
1379
        }
1380
    }
1381

    
1382
    private State transitionToState(State targetState, boolean checkSuspended) {
1383
        State currentState;
1384
        while((currentState = state.get()) != State.CLOSED &&
1385
                (!checkSuspended || checkSuspended(currentState)) &&
1386
                !state.compareAndSet(currentState, targetState)) {/**/}
1387
        return currentState;
1388
    }
1389

    
1390
    private boolean checkSuspended(State currentState) {
1391
        if (currentState == State.SUSPENDED) {
1392
            close();
1393
            throw DbException.get(ErrorCode.DATABASE_IS_IN_EXCLUSIVE_MODE);
1394
        }
1395
        return true;
1396
    }
1397

    
1398
    /**
1399
     * Check if the current transaction is canceled by calling
1400
     * Statement.cancel() or because a session timeout was set and expired.
1401
     *
1402
     * @throws DbException if the transaction is canceled
1403
     */
1404
    public void checkCanceled() {
1405
        throttle();
1406
        if (cancelAtNs == 0) {
1407
            return;
1408
        }
1409
        long time = System.nanoTime();
1410
        if (time >= cancelAtNs) {
1411
            cancelAtNs = 0;
1412
            throw DbException.get(ErrorCode.STATEMENT_WAS_CANCELED);
1413
        }
1414
    }
1415

    
1416
    /**
1417
     * Get the cancel time.
1418
     *
1419
     * @return the time or 0 if not set
1420
     */
1421
    public long getCancel() {
1422
        return cancelAtNs;
1423
    }
1424

    
1425
    public Command getCurrentCommand() {
1426
        return currentCommand;
1427
    }
1428

    
1429
    public ValueTimestampTimeZone getCurrentCommandStart() {
1430
        if (currentCommandStart == null) {
1431
            currentCommandStart = CurrentTimestamp.get();
1432
        }
1433
        return currentCommandStart;
1434
    }
1435

    
1436
    public boolean getAllowLiterals() {
1437
        return allowLiterals;
1438
    }
1439

    
1440
    public void setAllowLiterals(boolean b) {
1441
        this.allowLiterals = b;
1442
    }
1443

    
1444
    public void setCurrentSchema(Schema schema) {
1445
        modificationId++;
1446
        if (queryCache != null) {
1447
            queryCache.clear();
1448
        }
1449
        this.currentSchemaName = schema.getName();
1450
    }
1451

    
1452
    @Override
1453
    public String getCurrentSchemaName() {
1454
        return currentSchemaName;
1455
    }
1456

    
1457
    @Override
1458
    public void setCurrentSchemaName(String schemaName) {
1459
        Schema schema = database.getSchema(schemaName);
1460
        setCurrentSchema(schema);
1461
    }
1462

    
1463
    /**
1464
     * Create an internal connection. This connection is used when initializing
1465
     * triggers, and when calling user defined functions.
1466
     *
1467
     * @param columnList if the url should be 'jdbc:columnlist:connection'
1468
     * @return the internal connection
1469
     */
1470
    public JdbcConnection createConnection(boolean columnList) {
1471
        String url;
1472
        if (columnList) {
1473
            url = Constants.CONN_URL_COLUMNLIST;
1474
        } else {
1475
            url = Constants.CONN_URL_INTERNAL;
1476
        }
1477
        return new JdbcConnection(this, getUser().getName(), url);
1478
    }
1479

    
1480
    @Override
1481
    public DataHandler getDataHandler() {
1482
        return database;
1483
    }
1484

    
1485
    /**
1486
     * Remember that the given LOB value must be removed at commit.
1487
     *
1488
     * @param v the value
1489
     */
1490
    public void removeAtCommit(Value v) {
1491
        final String key = v.toString();
1492
        if (!v.isLinkedToTable()) {
1493
            DbException.throwInternalError(key);
1494
        }
1495
        if (removeLobMap == null) {
1496
            removeLobMap = new HashMap<>();
1497
        }
1498
        removeLobMap.put(key, v);
1499
    }
1500

    
1501
    /**
1502
     * Do not remove this LOB value at commit any longer.
1503
     *
1504
     * @param v the value
1505
     */
1506
    public void removeAtCommitStop(Value v) {
1507
        if (removeLobMap != null) {
1508
            removeLobMap.remove(v.toString());
1509
        }
1510
    }
1511

    
1512
    /**
1513
     * Get the next system generated identifiers. The identifier returned does
1514
     * not occur within the given SQL statement.
1515
     *
1516
     * @param sql the SQL statement
1517
     * @return the new identifier
1518
     */
1519
    public String getNextSystemIdentifier(String sql) {
1520
        String identifier;
1521
        do {
1522
            identifier = SYSTEM_IDENTIFIER_PREFIX + systemIdentifier++;
1523
        } while (sql.contains(identifier));
1524
        return identifier;
1525
    }
1526

    
1527
    /**
1528
     * Add a procedure to this session.
1529
     *
1530
     * @param procedure the procedure to add
1531
     */
1532
    public void addProcedure(Procedure procedure) {
1533
        if (procedures == null) {
1534
            procedures = database.newStringMap();
1535
        }
1536
        procedures.put(procedure.getName(), procedure);
1537
    }
1538

    
1539
    /**
1540
     * Remove a procedure from this session.
1541
     *
1542
     * @param name the name of the procedure to remove
1543
     */
1544
    public void removeProcedure(String name) {
1545
        if (procedures != null) {
1546
            procedures.remove(name);
1547
        }
1548
    }
1549

    
1550
    /**
1551
     * Get the procedure with the given name, or null
1552
     * if none exists.
1553
     *
1554
     * @param name the procedure name
1555
     * @return the procedure or null
1556
     */
1557
    public Procedure getProcedure(String name) {
1558
        if (procedures == null) {
1559
            return null;
1560
        }
1561
        return procedures.get(name);
1562
    }
1563

    
1564
    public void setSchemaSearchPath(String[] schemas) {
1565
        modificationId++;
1566
        this.schemaSearchPath = schemas;
1567
    }
1568

    
1569
    public String[] getSchemaSearchPath() {
1570
        return schemaSearchPath;
1571
    }
1572

    
1573
    @Override
1574
    public int hashCode() {
1575
        return serialId;
1576
    }
1577

    
1578
    @Override
1579
    public String toString() {
1580
        return "#" + serialId + " (user: " + (user == null ? "<null>" : user.getName()) + ", " + state.get() + ")";
1581
    }
1582

    
1583
    public void setUndoLogEnabled(boolean b) {
1584
        this.undoLogEnabled = b;
1585
    }
1586

    
1587
    public void setRedoLogBinary(boolean b) {
1588
        this.redoLogBinary = b;
1589
    }
1590

    
1591
    public boolean isUndoLogEnabled() {
1592
        return undoLogEnabled;
1593
    }
1594

    
1595
    /**
1596
     * Begin a transaction.
1597
     */
1598
    public void begin() {
1599
        autoCommitAtTransactionEnd = true;
1600
        autoCommit = false;
1601
    }
1602

    
1603
    public long getSessionStart() {
1604
        return sessionStart;
1605
    }
1606

    
1607
    public ValueTimestampTimeZone getTransactionStart() {
1608
        if (transactionStart == null) {
1609
            transactionStart = CurrentTimestamp.get();
1610
        }
1611
        return transactionStart;
1612
    }
1613

    
1614
    public Set<Table> getLocks() {
1615
        /*
1616
         * This implementation needs to be lock-free.
1617
         */
1618
        if (database.getLockMode() == Constants.LOCK_MODE_OFF || locks.isEmpty()) {
1619
            return Collections.emptySet();
1620
        }
1621
        /*
1622
         * Do not use ArrayList.toArray(T[]) here, its implementation is not
1623
         * thread-safe.
1624
         */
1625
        Object[] array = locks.toArray();
1626
        /*
1627
         * The returned array may contain null elements and may contain
1628
         * duplicates due to concurrent remove().
1629
         */
1630
        switch (array.length) {
1631
        case 1: {
1632
            Object table = array[0];
1633
            if (table != null) {
1634
                return Collections.singleton((Table) table);
1635
            }
1636
        }
1637
        //$FALL-THROUGH$
1638
        case 0:
1639
            return Collections.emptySet();
1640
        default: {
1641
            HashSet<Table> set = new HashSet<>();
1642
            for (Object table : array) {
1643
                if (table != null) {
1644
                    set.add((Table) table);
1645
                }
1646
            }
1647
            return set;
1648
        }
1649
        }
1650
    }
1651

    
1652
    /**
1653
     * Wait if the exclusive mode has been enabled for another session. This
1654
     * method returns as soon as the exclusive mode has been disabled.
1655
     */
1656
    public void waitIfExclusiveModeEnabled() {
1657
        transitionToState(State.RUNNING, true);
1658
        // Even in exclusive mode, we have to let the LOB session proceed, or we
1659
        // will get deadlocks.
1660
        if (database.getLobSession() == this) {
1661
            return;
1662
        }
1663
        while (isOpen()) {
1664
            Session exclusive = database.getExclusiveSession();
1665
            if (exclusive == null || exclusive == this) {
1666
                break;
1667
            }
1668
            if (Thread.holdsLock(exclusive)) {
1669
                // if another connection is used within the connection
1670
                break;
1671
            }
1672
            try {
1673
                Thread.sleep(100);
1674
            } catch (InterruptedException e) {
1675
                // ignore
1676
            }
1677
        }
1678
    }
1679

    
1680
    /**
1681
     * Get the view cache for this session. There are two caches: the subquery
1682
     * cache (which is only use for a single query, has no bounds, and is
1683
     * cleared after use), and the cache for regular views.
1684
     *
1685
     * @param subQuery true to get the subquery cache
1686
     * @return the view cache
1687
     */
1688
    public Map<Object, ViewIndex> getViewIndexCache(boolean subQuery) {
1689
        if (subQuery) {
1690
            // for sub-queries we don't need to use LRU because the cache should
1691
            // not grow too large for a single query (we drop the whole cache in
1692
            // the end of prepareLocal)
1693
            if (subQueryIndexCache == null) {
1694
                subQueryIndexCache = new HashMap<>();
1695
            }
1696
            return subQueryIndexCache;
1697
        }
1698
        SmallLRUCache<Object, ViewIndex> cache = viewIndexCache;
1699
        if (cache == null) {
1700
            viewIndexCache = cache = SmallLRUCache.newInstance(Constants.VIEW_INDEX_CACHE_SIZE);
1701
        }
1702
        return cache;
1703
    }
1704

    
1705
    /**
1706
     * Remember the result set and close it as soon as the transaction is
1707
     * committed (if it needs to be closed). This is done to delete temporary
1708
     * files as soon as possible, and free object ids of temporary tables.
1709
     *
1710
     * @param result the temporary result set
1711
     */
1712
    public void addTemporaryResult(ResultInterface result) {
1713
        if (!result.needToClose()) {
1714
            return;
1715
        }
1716
        if (temporaryResults == null) {
1717
            temporaryResults = new HashSet<>();
1718
        }
1719
        if (temporaryResults.size() < 100) {
1720
            // reference at most 100 result sets to avoid memory problems
1721
            temporaryResults.add(result);
1722
        }
1723
    }
1724

    
1725
    private void closeTemporaryResults() {
1726
        if (temporaryResults != null) {
1727
            for (ResultInterface result : temporaryResults) {
1728
                result.close();
1729
            }
1730
            temporaryResults = null;
1731
        }
1732
    }
1733

    
1734
    public void setQueryTimeout(int queryTimeout) {
1735
        int max = database.getSettings().maxQueryTimeout;
1736
        if (max != 0 && (max < queryTimeout || queryTimeout == 0)) {
1737
            // the value must be at most max
1738
            queryTimeout = max;
1739
        }
1740
        this.queryTimeout = queryTimeout;
1741
        // must reset the cancel at here,
1742
        // otherwise it is still used
1743
        this.cancelAtNs = 0;
1744
    }
1745

    
1746
    public int getQueryTimeout() {
1747
        return queryTimeout;
1748
    }
1749

    
1750
    /**
1751
     * Set the table this session is waiting for, and the thread that is
1752
     * waiting.
1753
     *
1754
     * @param waitForLock the table
1755
     * @param waitForLockThread the current thread (the one that is waiting)
1756
     */
1757
    public void setWaitForLock(Table waitForLock, Thread waitForLockThread) {
1758
        this.waitForLock = waitForLock;
1759
        this.waitForLockThread = waitForLockThread;
1760
    }
1761

    
1762
    public Table getWaitForLock() {
1763
        return waitForLock;
1764
    }
1765

    
1766
    public Thread getWaitForLockThread() {
1767
        return waitForLockThread;
1768
    }
1769

    
1770
    public int getModificationId() {
1771
        return modificationId;
1772
    }
1773

    
1774
    public Value getTransactionId() {
1775
        if (database.getStore() != null) {
1776
            if (transaction == null || !transaction.hasChanges()) {
1777
                return ValueNull.INSTANCE;
1778
            }
1779
            return ValueString.get(Long.toString(getTransaction().getSequenceNum()));
1780
        }
1781
        if (!database.isPersistent()) {
1782
            return ValueNull.INSTANCE;
1783
        }
1784
        if (undoLog == null || undoLog.size() == 0) {
1785
            return ValueNull.INSTANCE;
1786
        }
1787
        return ValueString.get(firstUncommittedLog + "-" + firstUncommittedPos +
1788
                "-" + id);
1789
    }
1790

    
1791
    /**
1792
     * Get the next object id.
1793
     *
1794
     * @return the next object id
1795
     */
1796
    public int nextObjectId() {
1797
        return objectId++;
1798
    }
1799

    
1800
    public boolean isRedoLogBinaryEnabled() {
1801
        return redoLogBinary;
1802
    }
1803

    
1804
    /**
1805
     * Get the transaction to use for this session.
1806
     *
1807
     * @return the transaction
1808
     */
1809
    public Transaction getTransaction() {
1810
        if (transaction == null) {
1811
            MVTableEngine.Store store = database.getStore();
1812
            if (store != null) {
1813
                if (store.getMvStore().isClosed()) {
1814
                    Throwable backgroundException = database.getBackgroundException();
1815
                    database.shutdownImmediately();
1816
                    throw DbException.get(ErrorCode.DATABASE_IS_CLOSED, backgroundException);
1817
                }
1818
                transaction = store.getTransactionStore().begin(this, this.lockTimeout, id);
1819
                transaction.setIsolationLevel(isolationLevel);
1820
            }
1821
            startStatement = -1;
1822
        }
1823
        return transaction;
1824
    }
1825

    
1826
    private long getStatementSavepoint() {
1827
        if (startStatement == -1) {
1828
            startStatement = getTransaction().setSavepoint();
1829
        }
1830
        return startStatement;
1831
    }
1832

    
1833
    /**
1834
     * Start a new statement within a transaction.
1835
     * @param command about to be started
1836
     */
1837
    @SuppressWarnings("incomplete-switch")
1838
    public void startStatementWithinTransaction(Command command) {
1839
        Transaction transaction = getTransaction();
1840
        if (transaction != null) {
1841
            HashSet<MVMap<?, ?>> currentMaps = null, allMaps = null;
1842
            if (command != null) {
1843
                Set<DbObject> dependencies = command.getDependencies();
1844
                currentMaps = new HashSet<>();
1845
                for (DbObject dependency : dependencies) {
1846
                    if (dependency instanceof MVTable) {
1847
                        addTableToDependencies((MVTable) dependency, currentMaps);
1848
                    }
1849
                }
1850
                switch (transaction.getIsolationLevel()) {
1851
                case REPEATABLE_READ: {
1852
                    allMaps = new HashSet<>();
1853
                    HashSet<MVTable> processed = new HashSet<>();
1854
                    for (DbObject dependency : dependencies) {
1855
                        if (dependency instanceof MVTable) {
1856
                            addTableToDependencies((MVTable) dependency, allMaps, processed);
1857
                        }
1858
                    }
1859
                    break;
1860
                }
1861
                case SNAPSHOT:
1862
                case SERIALIZABLE:
1863
                    if (!transaction.hasStatementDependencies()) {
1864
                        allMaps = new HashSet<>();
1865
                        for (Table table : database.getAllTablesAndViews(false)) {
1866
                            if (table instanceof MVTable) {
1867
                                addTableToDependencies((MVTable) table, allMaps);
1868
                            }
1869
                        }
1870
                    }
1871
                }
1872
            }
1873
            transaction.markStatementStart(currentMaps, allMaps);
1874
        }
1875
        startStatement = -1;
1876
        if (command != null) {
1877
            setCurrentCommand(command);
1878
        }
1879
    }
1880

    
1881
    private static void addTableToDependencies(MVTable table, HashSet<MVMap<?, ?>> maps) {
1882
        for (Index index : table.getIndexes()) {
1883
            if (index instanceof MVIndex) {
1884
                maps.add(((MVIndex) index).getMVMap());
1885
            }
1886
        }
1887
    }
1888

    
1889
    private static void addTableToDependencies(MVTable table, HashSet<MVMap<?, ?>> maps, HashSet<MVTable> processed) {
1890
        if (!processed.add(table)) {
1891
            return;
1892
        }
1893
        for (Index index : table.getIndexes()) {
1894
            if (index instanceof MVIndex) {
1895
                maps.add(((MVIndex) index).getMVMap());
1896
            }
1897
        }
1898
        for (Constraint constraint : table.getConstraints()) {
1899
            Table ref = constraint.getTable();
1900
            if (ref != table && ref instanceof MVTable) {
1901
                addTableToDependencies((MVTable) ref, maps, processed);
1902
            }
1903
        }
1904
    }
1905

    
1906
    /**
1907
     * Mark the statement as completed. This also close all temporary result
1908
     * set, and deletes all temporary files held by the result sets.
1909
     */
1910
    public void endStatement() {
1911
        setCurrentCommand(null);
1912
        if (transaction != null) {
1913
            transaction.markStatementEnd();
1914
        }
1915
        startStatement = -1;
1916
        closeTemporaryResults();
1917
    }
1918

    
1919
    /**
1920
     * Clear the view cache for this session.
1921
     */
1922
    public void clearViewIndexCache() {
1923
        viewIndexCache = null;
1924
    }
1925

    
1926
    @Override
1927
    public void addTemporaryLob(Value v) {
1928
        if (!DataType.isLargeObject(v.getValueType())) {
1929
            return;
1930
        }
1931
        if (v.getTableId() == LobStorageFrontend.TABLE_RESULT
1932
                || v.getTableId() == LobStorageFrontend.TABLE_TEMP) {
1933
            if (temporaryResultLobs == null) {
1934
                temporaryResultLobs = new LinkedList<>();
1935
            }
1936
            temporaryResultLobs.add(new TimeoutValue(v));
1937
        } else {
1938
            if (temporaryLobs == null) {
1939
                temporaryLobs = new ArrayList<>();
1940
            }
1941
            temporaryLobs.add(v);
1942
        }
1943
    }
1944

    
1945
    @Override
1946
    public boolean isRemote() {
1947
        return false;
1948
    }
1949

    
1950
    /**
1951
     * Mark that the given table needs to be analyzed on commit.
1952
     *
1953
     * @param table the table
1954
     */
1955
    public void markTableForAnalyze(Table table) {
1956
        if (tablesToAnalyze == null) {
1957
            tablesToAnalyze = new HashSet<>();
1958
        }
1959
        tablesToAnalyze.add(table);
1960
    }
1961

    
1962
    public State getState() {
1963
        return getBlockingSessionId() != 0 ? State.BLOCKED : state.get();
1964
    }
1965

    
1966
    public int getBlockingSessionId() {
1967
        return transaction == null ? 0 : transaction.getBlockerId();
1968
    }
1969

    
1970
    @Override
1971
    public void onRollback(MVMap<Object, VersionedValue> map, Object key,
1972
                            VersionedValue existingValue,
1973
                            VersionedValue restoredValue) {
1974
        // Here we are relying on the fact that map which backs table's primary index
1975
        // has the same name as the table itself
1976
        MVTableEngine.Store store = database.getStore();
1977
        if(store != null) {
1978
            MVTable table = store.getTable(map.getName());
1979
            if (table != null) {
1980
                long recKey = ((ValueLong)key).getLong();
1981
                Row oldRow = getRowFromVersionedValue(table, recKey, existingValue);
1982
                Row newRow = getRowFromVersionedValue(table, recKey, restoredValue);
1983
                table.fireAfterRow(this, oldRow, newRow, true);
1984

    
1985
                if (table.getContainsLargeObject()) {
1986
                    if (oldRow != null) {
1987
                        for (int i = 0, len = oldRow.getColumnCount(); i < len; i++) {
1988
                            Value v = oldRow.getValue(i);
1989
                            if (v.isLinkedToTable()) {
1990
                                removeAtCommit(v);
1991
                            }
1992
                        }
1993
                    }
1994
                    if (newRow != null) {
1995
                        for (int i = 0, len = newRow.getColumnCount(); i < len; i++) {
1996
                            Value v = newRow.getValue(i);
1997
                            if (v.isLinkedToTable()) {
1998
                                removeAtCommitStop(v);
1999
                            }
2000
                        }
2001
                    }
2002
                }
2003
            }
2004
        }
2005
    }
2006

    
2007
    private static Row getRowFromVersionedValue(MVTable table, long recKey,
2008
                                                VersionedValue versionedValue) {
2009
        Object value = versionedValue == null ? null : versionedValue.getCurrentValue();
2010
        if (value == null) {
2011
            return null;
2012
        }
2013
        Row result;
2014
        if(value instanceof Row) {
2015
            result = (Row) value;
2016
            assert result.getKey() == recKey : result.getKey() + " != " + recKey;
2017
        } else {
2018
            ValueArray array = (ValueArray) value;
2019
            result = table.createRow(array.getList(), 0);
2020
            result.setKey(recKey);
2021
        }
2022
        return result;
2023
    }
2024

    
2025

    
2026
    /**
2027
     * Represents a savepoint (a position in a transaction to where one can roll
2028
     * back to).
2029
     */
2030
    public static class Savepoint {
2031

    
2032
        /**
2033
         * The undo log index.
2034
         */
2035
        int logIndex;
2036

    
2037
        /**
2038
         * The transaction savepoint id.
2039
         */
2040
        long transactionSavepoint;
2041
    }
2042

    
2043
    /**
2044
     * An object with a timeout.
2045
     */
2046
    public static class TimeoutValue {
2047

    
2048
        /**
2049
         * The time when this object was created.
2050
         */
2051
        final long created = System.nanoTime();
2052

    
2053
        /**
2054
         * The value.
2055
         */
2056
        final Value value;
2057

    
2058
        TimeoutValue(Value v) {
2059
            this.value = v;
2060
        }
2061

    
2062
    }
2063

    
2064
    public ColumnNamerConfiguration getColumnNamerConfiguration() {
2065
        return columnNamerConfiguration;
2066
    }
2067

    
2068
    public void setColumnNamerConfiguration(ColumnNamerConfiguration columnNamerConfiguration) {
2069
        this.columnNamerConfiguration = columnNamerConfiguration;
2070
    }
2071

    
2072
    @Override
2073
    public boolean isSupportsGeneratedKeys() {
2074
        return true;
2075
    }
2076

    
2077
    /**
2078
     * Returns the network connection information, or {@code null}.
2079
     *
2080
     * @return the network connection information, or {@code null}
2081
     */
2082
    public NetworkConnectionInfo getNetworkConnectionInfo() {
2083
        return networkConnectionInfo;
2084
    }
2085

    
2086
    @Override
2087
    public void setNetworkConnectionInfo(NetworkConnectionInfo networkConnectionInfo) {
2088
        this.networkConnectionInfo = networkConnectionInfo;
2089
    }
2090

    
2091
    @Override
2092
    public ValueTimestampTimeZone currentTimestamp() {
2093
        return database.getMode().dateTimeValueWithinTransaction ? getTransactionStart() : getCurrentCommandStart();
2094
    }
2095

    
2096
    @Override
2097
    public Mode getMode() {
2098
        return database.getMode();
2099
    }
2100

    
2101
    @Override
2102
    public IsolationLevel getIsolationLevel() {
2103
        if (database.isMVStore()) {
2104
            return isolationLevel;
2105
        } else {
2106
            return IsolationLevel.fromLockMode(database.getLockMode());
2107
        }
2108
    }
2109

    
2110
    @Override
2111
    public void setIsolationLevel(IsolationLevel isolationLevel) {
2112
        commit(false);
2113
        if (database.isMVStore()) {
2114
            this.isolationLevel = isolationLevel;
2115
        } else {
2116
            int lockMode = isolationLevel.getLockMode();
2117
            org.h2.command.dml.Set set = new org.h2.command.dml.Set(this, SetTypes.LOCK_MODE);
2118
            set.setInt(lockMode);
2119
            synchronized (database) {
2120
                set.update();
2121
            }
2122
        }
2123
    }
2124

    
2125
}