Skip to content

Commit 0bc5e69

Browse files
committed
#118 add support for LIMIT & OFFSET
1 parent 12ccd88 commit 0bc5e69

File tree

13 files changed

+318
-55
lines changed

13 files changed

+318
-55
lines changed

.github/workflows/dub.yml

+16-10
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,32 @@ jobs:
1919
strategy:
2020
fail-fast: false
2121
matrix:
22-
os: [ ubuntu-latest, windows-latest, macos-latest ]
22+
os: [ ubuntu-latest, windows-latest ]
2323
compiler:
2424
- dmd-latest
2525
- ldc-latest
26-
- dmd-2.102.2
27-
- dmd-2.101.2
28-
- dmd-2.100.2
29-
- dmd-2.099.1
30-
- dmd-2.098.1
31-
- dmd-2.097.2
26+
- dmd-2.103.1 # (released in 2023)
27+
- dmd-2.102.2 # (released in 2023)
28+
- dmd-2.101.2 # (released in 2023)
29+
- dmd-2.100.2 # (released in 2022) ## GDC 12 can support 2.100
30+
- dmd-2.099.1 # (released in 2022)
31+
- dmd-2.098.1 # (released in 2021) ## Has issue re: phobos/std/variant.d
32+
- dmd-2.097.2 # (released in 2021)
33+
- ldc-1.33.0 # eq to dmd v2.103.1
34+
- ldc-1.32.2 # eq to dmd v2.102.2
3235
- ldc-1.28.1 # eq to dmd v2.098.1
3336
- ldc-1.27.1 # eq to dmd v2.097.2
37+
include:
38+
- { os: macos-latest, compiler: dmd-latest }
39+
- { os: macos-latest, compiler: ldc-latest }
40+
- { os: macos-latest, compiler: dmd-2.100.2 }
41+
- { os: macos-latest, compiler: ldc-1.32.2 }
3442
exclude:
3543
- { os: windows-latest, compiler: dmd-2.098.1 }
3644
- { os: windows-latest, compiler: dmd-2.097.2 }
37-
- { os: macos-latest, compiler: dmd-2.098.1 }
38-
- { os: macos-latest, compiler: dmd-2.097.2 }
3945

4046
steps:
41-
- uses: actions/checkout@v3
47+
- uses: actions/checkout@v4
4248

4349
- name: Install D ${{ matrix.compiler }}
4450
uses: dlang-community/setup-dlang@v1

README.md

+5
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,11 @@ foreach(ref e; stmt.select!User.where("id < 6").orderBy("name desc")) {
102102
writeln("id:", e.id, " name:", e.name, " flags:", e.flags);
103103
}
104104
105+
writeln("reading user table rows with where and order by with limit and offset");
106+
foreach(e; stmt.select!User.where("id < 6").orderBy("name desc").limit(3).offset(1)) {
107+
writeln("id:", e.id, " name:", e.name, " flags:", e.flags);
108+
}
109+
105110
writeln("reading all user table rows, but fetching only id and name (you will see default value 0 in flags field)");
106111
foreach(ref e; stmt.select!(User, "id", "name")) {
107112
writeln("id:", e.id, " name:", e.name, " flags:", e.flags);

docker-compose.yml

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ services:
77
#image: mariadb:latest
88
restart: always
99
ports: ['3306:3306', '33060:33060']
10+
ulimits:
11+
nofile:
12+
soft: "1024"
13+
hard: "10240"
1014
environment:
1115
- MYSQL_ROOT_PASSWORD=f48dfhw3Hd!Asah7i2aZ
1216
- MYSQL_DATABASE=testdb

example/dub.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"license": "Boost Software License (BSL 1.0)",
77
"dependencies": {
88
"ddbc": {"version": "~master", "path": "../"},
9-
"vibe-d": "~>0.9.4"
9+
"vibe-core": "1.22.6"
1010
},
1111
"targetType": "executable",
1212
"buildRequirements": [

example/source/testddbc.d

+43-11
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ int main(string[] args)
149149
}
150150
version( USE_SQLITE ) {
151151
driver = new SQLITEDriver();
152+
//url = chompPrefix(URI, "sqlite:");
153+
//url = SQLITEDriver.generateUrl();
152154
}
153155
url = chompPrefix(URI, "sqlite:");
154156
break;
@@ -368,7 +370,7 @@ int main(string[] args)
368370
int i = 0;
369371

370372
while (rs.next()) {
371-
writeln("\tid: " ~ to!string(rs.getLong(1)) ~ "\t" ~ rs.getString(2));
373+
writeln(" - id: " ~ to!string(rs.getLong(1)) ~ "\t" ~ rs.getString(2));
372374
i++;
373375
}
374376
writefln("\tThere were %,d rows returned from the ddbct1 table...", i);
@@ -383,7 +385,7 @@ int main(string[] args)
383385
rs = stmt.executeQuery("SELECT id,comment FROM ddbct1 WHERE id = 2");
384386
i = 0;
385387
while (rs.next()) {
386-
writeln("\tid: " ~ to!string(rs.getLong(1)) ~ "\t" ~ rs.getString(2));
388+
writeln(" - id: " ~ to!string(rs.getLong(1)) ~ "\t" ~ rs.getString(2));
387389
i++;
388390
}
389391
assert(1 == i, "There should be 1 result but instead there was " ~ to!string(i));
@@ -392,7 +394,7 @@ int main(string[] args)
392394
i = 0;
393395
rs = stmt.executeQuery("SELECT id, comment, ts FROM ddbct1 ORDER BY id DESC");
394396
while (rs.next()) {
395-
writeln("\tid: " ~ to!string(rs.getLong(1)) ~ "\t" ~ rs.getString(2) ~ "\t" ~ to!string(rs.getDateTime(3)));
397+
writeln(" - id: " ~ to!string(rs.getLong(1)) ~ "\t" ~ rs.getString(2) ~ "\t" ~ to!string(rs.getDateTime(3)));
396398
i++;
397399
}
398400
assert(2 == i, "There should be 2 results but instead there was " ~ to!string(i));
@@ -410,7 +412,7 @@ int main(string[] args)
410412
assert(rs.getDateTime(4).month == dtNow.month);
411413
assert(rs.getDateTime(4).day == dtNow.day);
412414
assert(rs.getDateTime(4).hour == dtNow.hour);
413-
writeln("\tid: " ~ to!string(rs.getLong(1)) ~ "\t" ~ rs.getString(2) ~ "\t" ~ to!string(rs.getDateTime(4)));
415+
writeln(" - id: " ~ to!string(rs.getLong(1)) ~ "\t" ~ rs.getString(2) ~ "\t" ~ to!string(rs.getDateTime(4)));
414416

415417
// ddbc should also allow you to retrive the timestamp as a SysTime (defaulting UTC if no zone info given)
416418
assert(rs.getSysTime(4).year == now.year);
@@ -420,14 +422,19 @@ int main(string[] args)
420422
i++;
421423
}
422424
assert(2 == i, "There should be 2 results but instead there was " ~ to!string(i));
425+
if(par.driver != "odbc") {
426+
// ODBC doesn't support ResultSet::getFetchSize()
427+
ulong fetchSize = rs.getFetchSize();
428+
assert(i == fetchSize, "ResultSet::getFetchSize() should have returned " ~ to!string(i) ~ " but was " ~ to!string(fetchSize));
429+
}
423430

424431
writeln("\n > Testing prepared SQL statements");
425432
PreparedStatement ps2 = conn.prepareStatement("SELECT id, name name_alias, comment, ts FROM ddbct1 WHERE id >= ?");
426433
scope(exit) ps2.close();
427434
ps2.setUlong(1, 1);
428435
auto prs = ps2.executeQuery();
429436
while (prs.next()) {
430-
writeln("\tid: " ~ to!string(prs.getLong(1)) ~ "\t" ~ prs.getString(2) ~ "\t" ~ prs.getString(3) ~ "\t" ~ to!string(prs.getDateTime(4)));
437+
writeln(" - id: " ~ to!string(prs.getLong(1)) ~ "\t" ~ prs.getString(2) ~ "\t" ~ prs.getString(3) ~ "\t" ~ to!string(prs.getDateTime(4)));
431438
}
432439

433440
writeln("\n > Testing basic POD support");
@@ -442,26 +449,51 @@ int main(string[] args)
442449
SysTime updated;
443450
}
444451

445-
immutable SysTime now = Clock.currTime();
452+
immutable DateTime now = cast(DateTime) Clock.currTime();
446453

447454
writeln(" > select all rows from employee table");
448455
foreach(ref e; conn.createStatement().select!Employee) {
449456
//SysTime nextMonth = now.add!"months"(1);
450457

451-
writeln("\t{id: ", e.id, ", name: ", e.name, ", flags: ", e.flags, ", dob: ", e.dob, ", created: ", e.created, ", updated: ", e.updated, "}");
458+
writeln(" - {id: ", e.id, ", name: ", e.name, ", flags: ", e.flags, ", dob: ", e.dob, ", created: ", e.created, ", updated: ", e.updated, "}");
452459
assert(e.name !is null);
453460
assert(e.dob.year > 1950);
454-
assert(e.created <= cast(DateTime) now);
455-
assert(e.updated <= now);
456-
}
461+
assert(e.created <= now);
462+
assert(cast(DateTime) e.updated <= now, "Updated '" ~ to!string(e.updated) ~ "' should be <= '" ~ to!string(now) ~ "'");
463+
}
457464

465+
i = 0;
458466
writeln(" > select all rows from employee table WHERE id < 4 ORDER BY name DESC...");
459467
foreach(ref e; conn.createStatement().select!Employee.where("id < 4").orderBy("name desc")) {
460-
writeln("\t{id: ", e.id, ", name: ", e.name, ", flags: ", e.flags, ", dob: ", e.dob, ", created: ", e.created, ", updated: ", e.updated, "}");
468+
writeln(" - {id: ", e.id, ", name: ", e.name, ", flags: ", e.flags, ", dob: ", e.dob, ", created: ", e.created, ", updated: ", e.updated, "}");
461469
assert(e.id < 4);
462470
assert(e.name != "Iain" && e.name != "Robert");
463471
assert(e.flags > 1);
472+
i++;
464473
}
474+
assert(3 == i, "There should be 3 results but instead there was " ~ to!string(i));
475+
476+
i = 0;
477+
writeln(" > select all rows from employee table WHERE id < 4 LIMIT 2...");
478+
foreach(ref e; conn.createStatement().select!Employee.where("id < 4").orderBy("id ASC").limit(2)) {
479+
writeln(" - {id: ", e.id, ", name: ", e.name, ", flags: ", e.flags, ", dob: ", e.dob, ", created: ", e.created, ", updated: ", e.updated, "}");
480+
assert(e.id == 1 || e.id == 2, "results should start from id 1 as there's no offset");
481+
assert(e.name != "Iain" && e.name != "Robert");
482+
assert(e.flags > 1);
483+
i++;
484+
}
485+
assert(2 == i, "There should be 2 results but instead there was " ~ to!string(i));
486+
487+
i = 0;
488+
writeln(" > select all rows from employee table WHERE id < 4 LIMIT 2 OFFSET 1...");
489+
foreach(ref e; conn.createStatement().select!Employee.where("id < 4").orderBy("id ASC").limit(2).offset(1)) {
490+
writeln(" - {id: ", e.id, ", name: ", e.name, ", flags: ", e.flags, ", dob: ", e.dob, ", created: ", e.created, ", updated: ", e.updated, "}");
491+
assert(e.id == 2 || e.id == 3, "results should start from id 2 due to offset");
492+
assert(e.name != "Iain" && e.name != "Robert");
493+
assert(e.flags > 1);
494+
i++;
495+
}
496+
assert(2 == i, "There should be 2 results but instead there was " ~ to!string(i));
465497

466498
// todo: Fix the UPDATE/INSERT functionality for PODs
467499
// Employee e;

source/ddbc/common.d

+8-1
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,12 @@ class ConnectionWrapper : Connection {
9494
this.pool = pool;
9595
this.base = base;
9696
}
97+
98+
// a db connection is DialectAware
99+
override Dialect getDialect() {
100+
return base.getDialect();
101+
}
102+
97103
override void close() {
98104
assert(!closed, "Connection is already closed");
99105
closed = true;
@@ -234,7 +240,7 @@ public:
234240
result = dg(cast(DataSetReader)this);
235241
if (result) break;
236242
} while (next());
237-
return result;
243+
return result;
238244
}
239245
override void close() {
240246
throw new SQLException("Method not implemented");
@@ -248,6 +254,7 @@ public:
248254
override bool isLast() {
249255
throw new SQLException("Method not implemented");
250256
}
257+
/// returns true if ResultSet object contains more rows
251258
override bool next() {
252259
throw new SQLException("Method not implemented");
253260
}

source/ddbc/core.d

+16-2
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ enum SqlType {
134134
VARCHAR,
135135
}
136136

137-
interface Connection {
137+
interface Connection : DialectAware {
138138
/// Releases this Connection object's database and JDBC resources immediately instead of waiting for them to be automatically released.
139139
void close();
140140
/// Makes all changes made since the previous commit/rollback permanent and releases any database locks currently held by this Connection object.
@@ -342,7 +342,21 @@ interface ResultSet : DataSetReader {
342342

343343
}
344344

345-
interface Statement {
345+
enum Dialect {
346+
SQLITE, // SQLite has it's own quirks
347+
MYSQL5, // for MySQL & MariaDB
348+
MYSQL8, // todo: add support for MySQL 8
349+
PGSQL, // PL/pgSQL (Procedural Language/PostgreSQL) used by PostgreSQL
350+
TSQL, // T-SQL (Transact-SQL) is Microsoft’s extension of SQL
351+
PLSQL // Oracle (PL/SQL)
352+
}
353+
354+
interface DialectAware {
355+
Dialect getDialect();
356+
}
357+
358+
// statements are made via a db connection which are also DialectAware
359+
interface Statement : DialectAware {
346360
ResultSet executeQuery(string query);
347361
int executeUpdate(string query);
348362
int executeUpdate(string query, out Variant insertId);

source/ddbc/drivers/mysqlddbc.d

+15
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ private:
119119

120120
public:
121121

122+
// db connections are DialectAware
123+
override Dialect getDialect() {
124+
return Dialect.MYSQL5; // TODO: add support for MySQL8
125+
}
126+
122127
void lock() {
123128
mutex.lock();
124129
}
@@ -306,6 +311,11 @@ private:
306311
bool closed;
307312

308313
public:
314+
// statements are DialectAware
315+
override Dialect getDialect() {
316+
return conn.getDialect();
317+
}
318+
309319
void checkClosed() {
310320
enforce!SQLException(!closed, "Statement is already closed");
311321
}
@@ -447,6 +457,11 @@ class MySQLPreparedStatement : MySQLStatement, PreparedStatement {
447457
}
448458
public:
449459

460+
// prepared statements are DialectAware
461+
override Dialect getDialect() {
462+
return conn.getDialect();
463+
}
464+
450465
/// Retrieves a ResultSetMetaData object that contains information about the columns of the ResultSet object that will be returned when this PreparedStatement object is executed.
451466
override ResultSetMetaData getMetaData() {
452467
checkClosed();

source/ddbc/drivers/odbcddbc.d

+20-10
Original file line numberDiff line numberDiff line change
@@ -336,21 +336,23 @@ version (USE_ODBC)
336336
}
337337
}
338338

339-
void checkClosed()
340-
{
339+
void checkClosed() {
341340
if (closed)
342341
throw new SQLException("Connection is already closed");
343342
}
344343

345344
public:
346345

347-
void lock()
348-
{
346+
// db connections are DialectAware
347+
override Dialect getDialect() {
348+
return Dialect.TSQL; // todo: handle TSQL and PLSQL
349+
}
350+
351+
void lock() {
349352
mutex.lock();
350353
}
351354

352-
void unlock()
353-
{
355+
void unlock() {
354356
mutex.unlock();
355357
}
356358

@@ -582,14 +584,17 @@ version (USE_ODBC)
582584
bool closed = false;
583585

584586
private SQLRETURN checkstmt(alias Fn, string file = __FILE__, size_t line = __LINE__)(
585-
Parameters!Fn args)
586-
{
587+
Parameters!Fn args) {
587588
return check!(Fn, file, line)(stmt, SQL_HANDLE_STMT, args);
588589
}
589590

590591
public:
591-
void checkClosed()
592-
{
592+
// statements are DialectAware
593+
override Dialect getDialect() {
594+
return conn.getDialect();
595+
}
596+
597+
void checkClosed() {
593598
enforce!SQLException(!closed, "Statement is already closed");
594599
}
595600

@@ -988,6 +993,11 @@ version (USE_ODBC)
988993

989994
public:
990995

996+
// prepared statements are DialectAware
997+
override Dialect getDialect() {
998+
return conn.getDialect();
999+
}
1000+
9911001
/// Retrieves a ResultSetMetaData object that contains information about the columns of the ResultSet object that will be returned when this PreparedStatement object is executed.
9921002
override ResultSetMetaData getMetaData()
9931003
{

0 commit comments

Comments
 (0)