データベース実装勉強会で読んだプログラムのメモ
先日、池袋バイナリ勉強会でのデータベース実装勉強会(http://connpass.com/event/13447/)に参加しました。
select * from test
をH2で実行する時に通るプログラムを読むという内容でした。
忘れてしまうのはもったいないので、少し読みなおしてメモ。
かなり憶測まじりな所があります。
/** * クエリ(select文)を実行し、result setを返す。 * もしこの文に他のresult setがある場合、これは閉じられるだろう(たとえこの文は失敗するとしても)(訳) */ @Override public ResultSet JdbcStatement.executeQuery(String sql) throws SQLException { try { int id = getNextId(TraceObject.RESULT_SET); // トレース用のresult setのID取得 if (isDebugEnabled()) { debugCodeAssign("ResultSet", TraceObject.RESULT_SET, id, "executeQuery(" + quote(sql) + ")"); } synchronized (session) { checkClosed(); // 接続が閉じているかチェック closeOldResultSet(); // 以前のresult setが開いていたら閉じる sql = JdbcConnection.translateSQL(sql, escapeProcessing); // JDBCエスケープシーケンスを変換する CommandInterface command = conn.prepareCommand(sql, fetchSize); // SQL文をパースする。キャッシュにあればそれを使う ResultInterface result; boolean scrollable = resultSetType != ResultSet.TYPE_FORWARD_ONLY; // result setがnext方向以外に移動できるか boolean updatable = resultSetConcurrency == ResultSet.CONCUR_UPDATABLE; // result setが更新可能か setExecutingStatement(command); // 実行中のSQL文をセットする try { result = command.executeQuery(maxRows, scrollable); // クエリを実行する(下に続く) } finally { setExecutingStatement(null); // 実行中のSQL文のセットを消す } command.close(); // SQL文を閉じる resultSet = new JdbcResultSet(conn, this, result, id, closedByResultSet, scrollable, updatable); // 実行結果を含んだresult setを作成 } return resultSet; } catch (Exception e) { throw logAndConvert(e); } }
/** * クエリを実行して結果を返す。このメソッドは全てを準備してから query(int) を最後に呼ぶ。(訳) */ @Override public ResultInterface Command.executeQuery(int maxrows, boolean scrollable) { startTime = 0; long start = 0; Database database = session.getDatabase(); Object sync = database.isMultiThreaded() ? (Object) session : (Object) database; // マルチスレッドモードか否かで同期の単位を選択 session.waitIfExclusiveModeEnabled(); // 排他モードなら必要に応じて他のセッションを待つ boolean callStop = true; // 最後にstopを呼ぶフラグ boolean writing = !isReadOnly(); // コマンドが書き込みを行うか if (writing) { while (!database.beforeWriting()) { // wait } } synchronized (sync) { session.setCurrentCommand(this); // セッションの現在のコマンドをセット try { while (true) { database.checkPowerOff(); // データベースのパワーオフ確認 try { return query(maxrows); // クエリの実行(下に続く) } catch (DbException e) { ... // 例外処理 } catch (OutOfMemoryError e) { ... // 例外処理 } catch (Throwable e) { ... // 例外処理 } } } catch (DbException e) { ... // 例外処理 } finally { if (callStop) { stop(); // 一時result setを閉じたり、一時ファイルを削除したり、コミットしたりする } if (writing) { database.afterWriting(); } } } }
@Override public ResultInterface CommandContainer.query(int maxrows) { recompileIfRequired(); // 必要ならクエリの再コンパイル setProgress(DatabaseEventListener.STATE_STATEMENT_START); // イベントリスナにクエリの実行開始を通知 start(); // トレース用の開始時間を得る prepared.checkParameters(); // パラメータのエラーチェック ResultInterface result = prepared.query(maxrows); // クエリの実行(下に続く) prepared.trace(startTime, result.getRowCount()); // トレース用処理 setProgress(DatabaseEventListener.STATE_STATEMENT_END); // イベントリスナにクエリの実行終了を通知 return result; }
@Override public LocalResult Query.query(int maxrows) { return query(maxrows, null); }
/** * クエリを実行して、結果をtarget resultに書く。 * * @param limit * 返す行の最大数 * @param target * the target result (nullの場合はresultを返す) * @return the result set (targetがセットされない場合). */ LocalResult Query.query(int limit, ResultTarget target) { fireBeforeSelectTriggers(); // トリガの実行 if (noCache || !session.getDatabase().getOptimizeReuseResults()) { return queryWithoutCache(limit, target); // キャッシュを使わずクエリ実行(下に続く) } Value[] params = getParameterValues(); // パラメータの値を得る long now = session.getDatabase().getModificationDataId(); if (isEverything(ExpressionVisitor.DETERMINISTIC_VISITOR)) { // 最後の結果(lastResult)を再利用できるか if (lastResult != null && !lastResult.isClosed() && limit == lastLimit) { if (sameResultAsLast(session, params, lastParameters, lastEvaluated)) { lastResult = lastResult.createShallowCopy(session); if (lastResult != null) { lastResult.reset(); return lastResult; } } } } lastParameters = params; closeLastResult(); LocalResult r = queryWithoutCache(limit, target); // 再利用できなかったのでキャッシュを使わずクエリ実行(下に続く) lastResult = r; this.lastEvaluated = now; lastLimit = limit; return r; }
@Override protected LocalResult Select.queryWithoutCache(int maxRows, ResultTarget target) { int limitRows = maxRows == 0 ? -1 : maxRows; // 指定最大行数が0なら制限なし(-1) if (limitExpr != null) { // LIMITやTOP等での行数の制限がある場合 Value v = limitExpr.getValue(session); int l = v == ValueNull.INSTANCE ? -1 : v.getInt(); if (limitRows < 0) { limitRows = l; } else if (l >= 0) { limitRows = Math.min(l, limitRows); } } int columnCount = expressions.size(); // 列数 LocalResult result = null; if (target == null || !session.getDatabase().getSettings().optimizeInsertFromSelect) { // 今回の経路だとtargetはNULL(戻り値resultを返すことを意味する) result = createLocalResult(result); // resultがnullの場合、LocalResult作成する関数 } if (sort != null && (!sortUsingIndex || distinct)) { // ORDER BY result = createLocalResult(result); result.setSortOrder(sort); // sort順をセットする } if (distinct && !isDistinctQuery) { // SELECT DISTINCT result = createLocalResult(result); result.setDistinct(); // distinctフラグをセットする } if (randomAccessResult) { // ランダムアクセスを要求する result = createLocalResult(result); result.setRandomAccess(); // random accessフラグをセットする } if (isGroupQuery && !isGroupSortedQuery) { // GROUP BY, HAVING result = createLocalResult(result); } if (limitRows >= 0 || offsetExpr != null) { // Max Rows, LIMIT, TOP, OFFSET result = createLocalResult(result); } topTableFilter.startQuery(session); // SQL文に含まれるテーブルに、セッションを設定 topTableFilter.reset(); // SQL文に含まれるテーブルの位置をリセット boolean exclusive = isForUpdate && !isForUpdateMvcc; // SELECT .. FOR UPDATE 等から排他ロックの必要性 if (isForUpdateMvcc) { // MVCC使用時にSELECT .. FOR UPDATEで、SELECTされた行のみロックする設定か if (isGroupQuery) { throw DbException .getUnsupportedException("FOR UPDATE && GROUP"); } else if (distinct) { throw DbException .getUnsupportedException("FOR UPDATE && DISTINCT"); } else if (isQuickAggregateQuery) { throw DbException .getUnsupportedException("FOR UPDATE && AGGREGATE"); } else if (topTableFilter.getJoin() != null) { throw DbException.getUnsupportedException("FOR UPDATE && JOIN"); } } topTableFilter.lock(session, exclusive, exclusive); // テーブルのロック ResultTarget to = result != null ? result : target; // ここまででresultが作成されていればそれを使う。そうでなければ引数のtargetを使う if (limitRows != 0) { // 返す最大行数が0の場合は、クエリを実行しない if (isQuickAggregateQuery) { // ダイレクトルックアップを使う集計クエリか queryQuick(columnCount, to); } else if (isGroupQuery) { // GROUP BY, HAVING if (isGroupSortedQuery) { // 不明 queryGroupSorted(columnCount, to); } else { queryGroup(columnCount, result); } } else if (isDistinctQuery) { // 不明 queryDistinct(to, limitRows); } else { queryFlat(columnCount, to, limitRows); // 今回のselect文ではここへ来る(下に続く) } } if (offsetExpr != null) { // LIMIT, OFFSET result.setOffset(offsetExpr.getValue(session).getInt()); // オフセット行数をセットする } if (limitRows >= 0) { // 返す行数が無制限でない result.setLimit(limitRows); // 返す行の最大数をセットする } if (result != null) { // resultがある場合 result.done(); // セットされたdistinct, external, sort, offset, limitを適用する if (target != null) { // 引数targetが指定されている場合はresultの内容をtargetに入れて返す while (result.next()) { target.addRow(result.currentRow()); } result.close(); return null; } return result; // targetが無いので戻り値resultを返す } return null; // resultが無いので引数targetで結果を返す }
private void Select.queryFlat(int columnCount, ResultTarget result, long limitRows) { // limitRowsはlong型でなければならない、そうでなければint型オーバーフローする // もしlimitRowsがInteger.MAX_VALUEか、それに近い値の場合に // ここではlimitRowsは0ではない(訳) if (limitRows > 0 && offsetExpr != null) { // 返す行数が制限されていて、オフセット指定がある場合 int offset = offsetExpr.getValue(session).getInt(); if (offset > 0) { limitRows += offset; } } int rowNumber = 0; setCurrentRowNumber(0); // 行のカウントを0に。キャンセルが有れば例外を投げる。128行毎にイベントリスナに現在の行数を通知する ArrayList<Row> forUpdateRows = null; if (isForUpdateMvcc) { // MVCC使用時にSELECT .. FOR UPDATEで、SELECTされた行のみロックする設定か forUpdateRows = New.arrayList(); // ロック指定のための行リスト } int sampleSize = getSampleSizeValue(session); // LIMIT SAMPLE_SIZE while (topTableFilter.next()) { // テーブルの次の行があるか(※ setCurrentRowNumber(rowNumber + 1); // 行のカウントをセット。キャンセルチェック。イベントリスナに通知。 if (condition == null // WHERE句等による条件に、合致する行か || Boolean.TRUE.equals(condition.getBooleanValue(session))) { Value[] row = new Value[columnCount]; for (int i = 0; i < columnCount; i++) { Expression expr = expressions.get(i); row[i] = expr.getValue(session); // 列の値を取得 } if (isForUpdateMvcc) { topTableFilter.lockRowAdd(forUpdateRows); // ロックする行として、現在の行を追加 } result.addRow(row); // クエリ結果に現在の行を追加 rowNumber++; // 出力行のカウントをインクリメント if ((sort == null || sortUsingIndex) && limitRows > 0 && result.getRowCount() >= limitRows) { break; // sortで順番の入れ替えが発生せず、最大行数まで結果を得たら抜ける } if (sampleSize > 0 && rowNumber >= sampleSize) { // 指定されたサンプリング行数を超えたら抜ける break; } } } if (isForUpdateMvcc) { topTableFilter.lockRows(forUpdateRows); // 行のロックをする } }
topTableFilter.next()
を追っていくと、がカーソルやページが出てきて難しい
コマンドクラス周辺
- 点線枠部がパースで生成される