プログラミング言語SML#解説 4.0.0版
22 SQL式とコマンド

22.1 SQLの型

22.1.1 SQLの基本型

NULLを除いて, 以下のSML#の基本型がSQLの基本型にそれぞれ対応する.

SML#の基本型 対応するSQLの型
int, intInf, word 整数型
bool SQL:99(機能ID T031)のBOOLEAN型
char CHAR(1)
string TEXTまたはVARCHAR
real 倍精度浮動小数点型
real32 単精度浮動小数点型

さらに,以下の数値型がSQLとの相互運用のために定義されている.

SML#の型 対応するSQLの型
SQL.numeric NUMERIC型(最大精度の10進数)
SQL.decimal DECIMAL型(NUMERIC型の別名)

SML#の型とデータベース側の型の具体的な対応付けは, 接続先のデータベースエンジンと, SML#におけるそのデータベースエンジンのサポート状況に依存する. データベースエンジンごとの具体的な型の対応は 22.8.1節で示す.

SQLのNULLは,SML#ではoption型のNONEに 対応付けられる. NOT NULL制約が無いカラムの参照など,NULLを返す 可能性がある式の型は,上記の型のいずれかのoption型である.

22.1.2 SQLの論理演算式の型

SQL.bool3型は,比較演算子や論理演算子からなるSQLの論理演算式 の型である. このSQL.bool3型は,SQLクエリの静的な整合性検査のために, 真偽値に評価されるSQL評価式の断片に便宜上付けた型であり, どの標準SQLの型とも対応しない.

論理演算式の型SQL.bool3と真偽値の型boolを 区別していることは, SQLの真偽値の取り扱いに関して歴史的な混乱があることに由来している. SQLでは伝統的に,真偽判定は真,偽,不明の3値で行われ, 真偽値は第一級ではなく(例えば真偽値をテーブルに保存することはできない), 真偽値のリテラルも存在しなかった. 第一級の真偽値型BOOLEANは,SQL99において,オプショナルな機能 (機能ID T031)として導入された. しかし,このオプショナルな規格にはSQLの他の部分との間で技術的な 不整合があることが指摘されており, 結局,このBOOLEAN型はRDBMSベンダーからの支持をほとんど集めなかった. 規格策定から20年近く立った現在においても, 主要なRDBMSは,唯一の例外であるPostgreSQLを除いて, BOOLEAN型を提供していない.

この混乱から来る誤解や非互換性を避けるため,SML#は 論理演算式と真偽値の区別を型上で強制する. 真理値リテラル trueおよびfalseは 論理演算式として使用することはできず, また 論理演算の結果をBOOLEAN値として取り出すことはできない. 真偽値リテラルについては 22.4.2節を, SQL論理演算式については 22.4.6節を 参照せよ.

22.1.3 SQLのテーブルおよびスキーマの型

SQLのテーブル,ビュー,およびスキーマには, SML#のレコードのリスト型が対応付けられる. SQLのテーブルおよびビューには, カラムの名前と型をそれぞれフィールドラベルおよびそのフィールドの型 としたレコード型のリスト型が与えられる. カラムに NOT NULL制約またはPRIMARY KEY制約が付いていないとき, そのカラムの型はいずれかの基本型のoption型である. 例えば,

CREATE TABLE foo (bar INT, baz TEXT NOT NULL);

と定義されたテーブルfooの構造は,SML#では

{bar : int option, baz : string} list

という型で表現される.

このように,テーブルあるいはカラムに付く制約のうち, NOT NULL制約およびPRIMARY KEY制約の「NULLではない」ことだけは SML#の型で表現され, SML#の型システムによって静的に検査される. その他の制約はSQLデータベースに よってクエリ実行時に検査される.

SQLスキーマは,テーブルおよびビューの名前をフィールドラベル, その構造を表す型をフィールド型とするレコード型で表現される. 例えば,

CREATE TABLE employee (id INT PRIMARY KEY, name TEXT NOT NULL,
                       age INT NOT NULL, deptId INT, salary INT);
CREATE TABLE department (deptId INT PRIMARY KEY, name TEXT NOT NULL);

と定義されたスキーマは,SML#では

{
  employee : {id : int, name : string, age : int,
              deptId : int option, salary : int option} list,
  department : {deptId : int, name : string} list
}

と表現される.

22.1.4 SQLクエリおよびその断片の型

SQLクエリおよびその部分式は,その構文カテゴリに対応して, それぞれ異なる型を持つ. SQLの各構文カテゴリとその型は以下の通りである.

構文カテゴリ その項の型
SQL評価式 (τ1 -> τ2,w) SQL.exp
SQLコマンド (τ,w) SQL.command
SELECTクエリ (τ,w) SQL.query
SELECT句 (τ1,τ2,w) SQL.select
FROM句 (τ,w) SQL.from
WHERE句 (τ,w) SQL.whr
ORDER BY句 (τ,w) SQL.orderby
OFFSET句 (τ,w) SQL.offset
LIMIT句 (τ,w) SQL.limit

ここで,τはテーブルの型あるいは基本型, wは接続先データベースを識別する型である. 各型の意図は以下の通りである.

  • (τ1 -> τ2, w) SQL.exp は, データベース接続wおよびτ1型の行の下でτ2型を持つ SQL評価式の型である.

  • (τ1,τ2,w) SQL.select は, データベース接続wの下で τ1型のテーブルをτ2型のテーブルに変換する SELECT句の型である.

  • これら以外の構文カテゴリの型 (τ,w) SQL.Xは, データベース接続wの下でτ型を持つXの型である.

22.1.5 SQL関連のハンドルの型

SQLデータベースへの接続や,SQLクエリの結果に対して, SML#は以下の型を与える.

説明
τ SQL.server τ型のスキーマを持つデータベースを管理するサーバーへの接続先
τ SQL.conn τ型のスキーマを持つデータベースを管理するサーバーへの接続ハンドル
τ SQL.cursor τ型のテーブルにアクセスするためのカーソル
(τ,w) SQL.db 接続wで接続されているτ型のスキーマを持つデータベースの実体

SQLを用いる典型的なSML#プログラムでは, これらの型はおおよそ以下のように使用される.

  1. 1.

    _sqlserver構文を用いて, τ SQL.server型の接続先情報を作る. (22.3節参照)

  2. 2.

    SQL.connect関数を用いて, τ SQL.server型の接続先に接続し, τ SQL.conn型の接続ハンドルを得る. (22.8.1節参照)

  3. 3.

    SQLクエリを, [’a. (τ,’a) SQL.db -> (τ SQL.cursor,’a) SQL.command]型を 持つ多相関数として構築する (22.1.6節参照).

  4. 4.

    _sql構文を用いて, SQLクエリを τ SQL.conn -> τ SQL.cursor型の 関数に変換する (22.7節参照).

  5. 5.

    この関数をτ SQL.conn型の接続ハンドルを引数として 呼び出すと, SQLクエリがサーバーに送信され,クエリがサーバーで評価される. クエリの評価に成功すると, 評価結果にアクセスするτ SQL.cursor型のカーソルが得られる (22.7節参照).

  6. 6.

    SQL.fetch関数またはSQL.fetchAll関数を用いて τ SQL.cursor型のカーソルから τ型のレコードを取得する (22.8.2節参照).

22.1.6 SQL式の型付け方針

SML#のSQL式は, これまでに定義した基本型の対応,論理演算式型の導入, テーブル構造とレコードのリスト型の対応付けを用いて 型付けされる. SQLクエリは,それが行うテーブル処理と同じことを レコードのリストに対して行うSML#のプログラムと 同型の型を持つ.

例えば,

SELECT t.name AS employeeName, t.age AS employeeAge
FROM employeeTable AS t
WHERE t.age > 20

というSQLクエリを,SML#では

val Q = fn db => _sql select #t.name as employeeName, #t.age as employeeAge
                      from #db.employeeTable as t
                      where #t.age > 20

と書く. 元のSQLには現れない変数dbは, クエリが実行される対象のデータベースを抽象する変数である.

さて,このクエリは, FROM句が参照するテーブルの各行をWHERE句の条件でフィルタにかけ, 残った各行をSELECT句の式で変形したテーブルを計算するクエリである. このクエリと同じことを,SQLのテーブルの代わりに SML#のレコードのリストに対して行うSML#プログラムを単純に書くと, 以下のようになる.

val Q’ =
  fn db => List.map
             (fn x => {employeeName = #name (#t x), employeeAge = #age (#t x)})
             (List.filter
                (fn x => #age (#t x) > 20)
                (List.map
                   (fn x => {t = x})
                   (#employeeTable db)))

この関数Q’の型は

val Q’ : [’a#{employeeTable : ’b list},
          ’b#{age : int, name : ’c},
          ’c.
          ’a -> {employeeAge : ’c, employeeName : int} list]

である. この関数Q’のことを,SQLクエリQのトイプログラムと 呼ぶ.

このSQLクエリとSML#式の対応を通じて, 上述のクエリQには, 「このリストを扱うSML#プログラムと同等のことを データベースサーバーが実行するSQLクエリ」を表す以下の型が与えられる.

val Q : [’a#{employeeTable : ’b list},
         ’b#{age : int, name : ’c},
         ’c::{int, ...}, ’d.
         (’a, ’d) SQL.db -> ({employeeAge : ’c, employeeName : int} list, ’d) SQL.query]

このQの型に現れるカインド付き型変数は, このクエリの以下の性質をそれぞれ表している.

  • ’aは,このクエリが対象とするデータベースには少なくとも ’b型のemployeeTableテーブルがなくてはならない ことを表す. それ以外のテーブルの存在はこのクエリの評価に関係しない.

  • ’bは,employeeTableテーブルには少なくとも int型のageカラムと ’cの型のnameカラムがなくてはならないことを表す. それ以外のカラムがemployeeTableテーブルにあってもよい.

  • ’cは,nameカラムの型はSQL基本型の範囲で 任意であることを表す (実際には,’cのカインドから参照される型変数がもうひとつ 存在するが,ここでは省略する).

  • ’dは,このクエリが任意の接続ハンドルを通じて サーバーに送信できることを表す (1つのクエリが複数の送信先にまたがって書かれていないことを検査するために 用いられる).

Qにレコード多相型が付いていることから分かる通り, SQLクエリはデータベースに対して多相的である. SML#は,対象のデータベースが抽象されたSQLクエリに 対して,そのSQLクエリが持つ最も一般的な型を推論する.

SQLクエリ全体と同様に, SQLクエリのいくつかの断片についても, レコードとリストを扱うSML#式との自然な対応を 考えることができる. 本章で定義するSQL関連構文の型付け規則はすべて, 自然な対応に従って定められている.