Executing Statements¶
Connection.exec() runs a SQL statement with no parameters and no result set you want to fetch — DDL (CREATE, DROP, ALTER) and ad-hoc DML where you only care about the affected row count.
fun ref exec(sql: String val): (RowCount | ExecError)
RowCountis(USize | NoRowCount).USizeis the affected row count when the driver reports one.NoRowCountmeans the driver returnedSQL_NO_ROW_COUNT(-1). Most DDL lands here.ExecErroris the error branch.
So a full match has three arms. \exhaustive\ makes the compiler enforce them.
Creating a table and inserting rows¶
use "lib:odbc"
use "odbc"
actor Main
new create(env: Env) =>
let dsn_name =
try env.args(1)?
else "psqlred"
end
match Odbc.connect(Dsn("DSN=" + dsn_name))
| let conn: Connection =>
// DDL: exec returns RowCount | NoRowCount | ExecError.
match conn.exec("DROP TABLE IF EXISTS tut_exec")
| let e: ExecError => env.err.print("drop: " + e.string())
end
let ct =
"CREATE TABLE tut_exec ("
+ "id INTEGER, name VARCHAR(32))"
match \exhaustive\ conn.exec(ct)
| let _: USize => env.out.print("created")
| NoRowCount => env.out.print("created (no row count)")
| let e: ExecError =>
env.err.print("create: " + e.string())
conn.close()
return
end
// DML: USize branch reports affected row count.
let ins =
"INSERT INTO tut_exec VALUES (1, 'widget'), (2, 'gadget')"
match \exhaustive\ conn.exec(ins)
| let n: USize => env.out.print("inserted " + n.string() + " rows")
| NoRowCount => env.out.print("inserted (no row count)")
| let e: ExecError => env.err.print("insert: " + e.string())
end
conn.exec("DROP TABLE IF EXISTS tut_exec")
conn.close()
| let e: ConnectError =>
env.err.print("connect: " + e.string())
end
./build/02-exec
created
inserted 2 rows
Matching on the three arms¶
match \exhaustive\ conn.exec(ct)
| let _: USize => env.out.print("created")
| NoRowCount => env.out.print("created (no row count)")
| let e: ExecError =>
env.err.print("create: " + e.string())
conn.close()
return
end
On Postgres, CREATE TABLE returns NoRowCount. Other drivers might return USize 0. The branch structure makes that difference explicit.
Partial match when you don’t care¶
The sample’s first DROP TABLE IF EXISTS uses a non-exhaustive match:
match conn.exec("DROP TABLE IF EXISTS tut_exec")
| let e: ExecError => env.err.print("drop: " + e.string())
end
An ordinary match is allowed to be partial. A good fit when you only want to know about surprising failures.
exec is for statements without a result set¶
exec can run a SELECT, but the rows are discarded. For anything where you want them back, use query().
Partial variant for chaining¶
Every non-trivial Connection method has a _p variant that raises on error:
try
conn.exec_p("CREATE TABLE t (id INTEGER)")?
conn.exec_p("INSERT INTO t VALUES (1)")?
conn.exec_p("INSERT INTO t VALUES (2)")?
else
env.err.print("setup failed")
end
Convenient when any failure should abort a block. The else loses kind/diagnostic detail — reach for _p when you only need whether, not how.