denizzzka / dpq2
1
/// Dealing with results of queries
2
module dpq2.result;
3

4
public import dpq2.conv.to_d_types;
5
public import dpq2.conv.to_bson;
6
public import dpq2.oids;
7
public import dpq2.value;
8

9
import dpq2.connection: Connection;
10
import dpq2.args: QueryParams;
11
import dpq2.exception;
12
import derelict.pq.pq;
13

14
import core.vararg;
15
import std.string: toStringz;
16
import std.exception: enforce;
17
import core.exception: OutOfMemoryError;
18
import std.bitmanip: bigEndianToNative;
19
import std.conv: to;
20

21
/// Result table's cell coordinates
22
private struct Coords
23
{
24
    size_t row; /// Row
25
    size_t col; /// Column
26
}
27

28
package immutable final class ResultContainer
29
{
30
    // ResultContainer allows only one copy of PGresult* due to avoid double free.
31
    // For the same reason this class is declared as final.
32
    private PGresult* result;
33
    alias result this;
34

35
    nothrow invariant()
36
    {
37 0
        assert( result != null );
38
    }
39

40 0
    package this(immutable PGresult* r)
41
    {
42 0
        assert(r);
43

44 0
        result = r;
45
    }
46

47
    ~this()
48
    {
49 0
        assert(result != null);
50

51 0
        PQclear(result);
52
    }
53
}
54

55
/// Contains result of query regardless of whether it contains an error or data answer
56
immutable class Result
57
{
58
    private ResultContainer result;
59

60 0
    package this(immutable ResultContainer r)
61
    {
62 0
        result = r;
63
    }
64

65
    /// Returns the result status of the command.
66
    ExecStatusType status() nothrow
67
    {
68 0
        return PQresultStatus(result);
69
    }
70

71
    /// Text description of result status.
72
    string statusString()
73
    {
74 0
        return PQresStatus(status).to!string;
75
    }
76

77
    /// Returns the error message associated with the command, or an empty string if there was no error.
78
    string resultErrorMessage()
79
    {
80 0
        return PQresultErrorMessage(result).to!string;
81
    }
82

83
    /// Returns an individual field of an error report.
84
    string resultErrorField(int fieldcode)
85
    {
86 0
        return PQresultErrorField(result, fieldcode).to!string;
87
    }
88

89
    /// Creates Answer object
90
    immutable(Answer) getAnswer()
91
    {
92 0
        return new immutable Answer(result);
93
    }
94

95
    ///
96
    string toString()
97
    {
98
        import std.ascii: newline;
99

100 0
        string err = resultErrorMessage();
101

102 0
        return statusString()~(err.length != 0 ? newline~err : "");
103
    }
104
}
105

106
/// Contains result of query with valid data answer
107
immutable class Answer : Result
108
{
109 0
    package this(immutable ResultContainer r)
110
    {
111 0
        super(r);
112

113 0
        checkAnswerForErrors();
114
    }
115

116
    private void checkAnswerForErrors()
117
    {
118 0
        switch(status)
119
        {
120 0
            case PGRES_COMMAND_OK:
121 0
            case PGRES_TUPLES_OK:
122 0
            case PGRES_SINGLE_TUPLE:
123 0
            case PGRES_COPY_IN:
124 0
                break;
125

126 0
            case PGRES_COPY_OUT:
127 0
                throw new AnswerException(ExceptionType.COPY_OUT_NOT_IMPLEMENTED, "COPY TO not yet supported");
128

129 0
            default:
130 0
                throw new ResponseException(this, __FILE__, __LINE__);
131
        }
132
    }
133

134
    /**
135
     * Returns the command status tag from the SQL command that generated the PGresult
136
     * Commonly this is just the name of the command, but it might include
137
     * additional data such as the number of rows processed.
138
     */
139
    string cmdStatus()
140
    {
141 0
        return (cast(PGresult*) result.result).PQcmdStatus.to!string;
142
    }
143

144
    /**
145
     * Returns the number of rows affected by the SQL command.
146
     * This function returns a string containing the number of rows affected by the SQL statement
147
     * that generated the Answer. This function can only be used following the execution of
148
     * a SELECT, CREATE TABLE AS, INSERT, UPDATE, DELETE, MOVE, FETCH, or COPY statement,
149
     * or an EXECUTE of a prepared query that contains an INSERT, UPDATE, or DELETE statement.
150
     * If the command that generated the Anwser was anything else, cmdTuples returns an empty string.
151
    */
152
    string cmdTuples()
153
    {
154 0
        return PQcmdTuples(cast(PGresult*) result.result).to!string;
155
    }
156

157
    /// Returns row count
158 0
    size_t length() nothrow { return PQntuples(result); }
159

160
    /// Returns column count
161 0
    size_t columnCount() nothrow { return PQnfields(result); }
162

163
    /// Returns column format
164
    ValueFormat columnFormat( const size_t colNum )
165
    {
166 0
        assertCol( colNum );
167

168 0
        return cast(ValueFormat) PQfformat(result, to!int(colNum));
169
    }
170

171
    /// Returns column Oid
172
    OidType OID( size_t colNum )
173
    {
174 0
        assertCol( colNum );
175

176 0
        return PQftype(result, to!int(colNum)).oid2oidType;
177
    }
178

179
    /// Checks if column type is array
180
    bool isArray( const size_t colNum )
181
    {
182 0
        assertCol(colNum);
183

184 0
        return dpq2.oids.isSupportedArray(OID(colNum));
185
    }
186
    alias isSupportedArray = isArray; //TODO: deprecated
187

188
    /// Returns column number by field name
189
    size_t columnNum( string columnName )
190
    {
191 0
        size_t n = PQfnumber(result, toStringz(columnName));
192

193 0
        if( n == -1 )
194 0
            throw new AnswerException(ExceptionType.COLUMN_NOT_FOUND,
195
                    "Column '"~columnName~"' is not found", __FILE__, __LINE__);
196

197 0
        return n;
198
    }
199

200
    /// Returns column name by field number
201
    string columnName( in size_t colNum )
202
    {
203 0
        const char* s = PQfname(result, colNum.to!int);
204

205 0
        if( s == null )
206 0
            throw new AnswerException(
207
                    ExceptionType.OUT_OF_RANGE,
208
                    "Column "~to!string(colNum)~" is out of range 0.."~to!string(columnCount),
209
                    __FILE__, __LINE__
210
                );
211

212 0
        return s.to!string;
213
    }
214

215
    /// Returns true if the column exists, false if not
216
    bool columnExists( string columnName )
217
    {
218 0
        size_t n = PQfnumber(result, columnName.toStringz);
219

220 0
        return n != -1;
221
    }
222

223
    /// Returns row of cells
224
    immutable (Row) opIndex(in size_t row)
225
    {
226 0
        return immutable Row(this, row);
227
    }
228

229
    /**
230
     Returns the number of parameters of a prepared statement.
231
     This function is only useful when inspecting the result of describePrepared.
232
     For other types of queries it will return zero.
233
    */
234
    uint nParams()
235
    {
236 0
        return PQnparams(result);
237
    }
238

239
    /**
240
     Returns the data type of the indicated statement parameter.
241
     Parameter numbers start at 0.
242
     This function is only useful when inspecting the result of describePrepared.
243
     For other types of queries it will return zero.
244
    */
245
    OidType paramType(T)(T paramNum)
246
    {
247 0
        return PQparamtype(result, paramNum.to!uint).oid2oidType;
248
    }
249

250
    ///
251
    override string toString()
252
    {
253
        import std.ascii: newline;
254

255 0
        string res;
256

257 0
        foreach(n; 0 .. columnCount)
258 0
            res ~= columnName(n)~"::"~OID(n).to!string~"\t";
259

260 0
        res ~= newline;
261

262 0
        foreach(row; rangify(this))
263 0
            res ~= row.toString~newline;
264

265 0
        return super.toString~newline~res;
266
    }
267

268
    private void assertCol( const size_t c )
269
    {
270 0
        if(!(c < columnCount))
271 0
            throw new AnswerException(
272
                ExceptionType.OUT_OF_RANGE,
273
                "Column "~to!string(c)~" is out of range 0.."~to!string(columnCount)~" of result columns",
274
                __FILE__, __LINE__
275
            );
276
    }
277

278
    private void assertRow( const size_t r )
279
    {
280 0
        if(!(r < length))
281 0
            throw new AnswerException(
282
                ExceptionType.OUT_OF_RANGE,
283
                "Row "~to!string(r)~" is out of range 0.."~to!string(length)~" of result rows",
284
                __FILE__, __LINE__
285
            );
286
    }
287

288
     private void assertCoords( const Coords c )
289
    {
290 0
        assertRow( c.row );
291 0
        assertCol( c.col );
292
    }
293
}
294

295
/// Creates forward range from immutable Answer
296
auto rangify(T)(T obj)
297
{
298
    struct Rangify(T)
299
    {
300
        T obj;
301
        alias obj this;
302

303
        private int curr;
304

305 0
        this(T o)
306
        {
307 0
            obj = o;
308
        }
309

310 0
        auto front(){ return obj[curr]; }
311 0
        void popFront(){ ++curr; }
312 0
        bool empty(){ return curr >= obj.length; }
313
    }
314

315 0
    return Rangify!(T)(obj);
316
}
317

318
/// Represents one row from the answer table
319
immutable struct Row
320
{
321
    private Answer answer;
322
    private size_t row;
323

324
    ///
325 0
    this(immutable Answer answer, in size_t row)
326
    {
327 0
        answer.assertRow( row );
328

329 0
        this.answer = answer;
330 0
        this.row = row;
331
    }
332

333
    /// Returns the actual length of a cell value in bytes.
334
    size_t size( const size_t col )
335
    {
336 0
        answer.assertCol(col);
337

338 0
        return PQgetlength(answer.result, to!int(row), to!int(col));
339
    }
340

341
    /// Checks if value is NULL
342
    ///
343
    /// Do not confuse it with Nullable's isNull method
344
    bool isNULL( const size_t col )
345
    {
346 0
        answer.assertCol(col);
347

348 0
        return PQgetisnull(answer.result, to!int(row), to!int(col)) != 0;
349
    }
350

351
    /// Checks if column with name exists
352
    bool columnExists(in string column)
353
    {
354 0
        return answer.columnExists(column);
355
    }
356

357
    /// Returns cell value by column number
358
    immutable (Value) opIndex(in size_t col)
359
    {
360 0
        answer.assertCoords( Coords( row, col ) );
361

362
        // The pointer returned by PQgetvalue points to storage that is part of the PGresult structure.
363
        // One should not modify the data it points to, and one must explicitly copy the data into other
364
        // storage if it is to be used past the lifetime of the PGresult structure itself.
365 0
        immutable ubyte* v = cast(immutable) PQgetvalue(answer.result, to!int(row), to!int(col));
366 0
        size_t s = size(col);
367

368 0
        return immutable Value(v[0..s], answer.OID(col), isNULL(col), answer.columnFormat(col));
369
    }
370

371
    /// Returns cell value by field name
372
    immutable (Value) opIndex(in string column)
373
    {
374 0
        return opIndex(columnNum(column));
375
    }
376

377
    /// Returns column number by field name
378
    size_t columnNum( string columnName )
379
    {
380 0
        return answer.columnNum( columnName );
381
    }
382

383
    /// Returns column name by field number
384
    string columnName( in size_t colNum )
385
    {
386 0
        return answer.columnName( colNum );
387
    }
388

389
    /// Returns column count
390 0
    size_t length() { return answer.columnCount(); }
391

392
    ///
393
    string toString()
394
    {
395 0
        string res;
396

397 0
        foreach(val; rangify(this))
398 0
            res ~= dpq2.result.toString(val)~"\t";
399

400 0
        return res;
401
    }
402
}
403

404
/// Creates Array from appropriate Value
405
immutable (Array) asArray(immutable(Value) v)
406
{
407 1
    if(v.format == ValueFormat.TEXT)
408 0
        throw new ValueConvException(ConvExceptionType.NOT_ARRAY,
409
            "Value internal format is text",
410
            __FILE__, __LINE__
411
        );
412

413 1
    if(!v.isSupportedArray)
414 0
        throw new ValueConvException(ConvExceptionType.NOT_ARRAY,
415
            "Format of the value is "~to!string(v.oidType)~", isn't supported array",
416
            __FILE__, __LINE__
417
        );
418

419 1
    return immutable Array(v);
420
}
421

422
///
423
string toString(immutable Value v)
424
{
425
    import vibe.data.bson: Bson;
426

427 0
    return v.isNull ? "NULL" : v.as!Bson.toString;
428
}
429

430
package struct ArrayHeader_net // network byte order
431
{
432
    ubyte[4] ndims; // number of dimensions of the array
433
    ubyte[4] dataoffset_ign; // offset for data, removed by libpq. may be it contains isNULL flag!
434
    ubyte[4] OID; // element type OID
435
}
436

437
package struct Dim_net // network byte order
438
{
439
    ubyte[4] dim_size; // number of elements in dimension
440
    ubyte[4] lbound; // unknown
441
}
442

443
private @safe struct BytesReader(A = const ubyte[])
444
{
445
    A arr;
446
    size_t currIdx;
447

448 1
    this(A a)
449
    {
450 1
        arr = a;
451
    }
452

453
    T* read(T)() @trusted
454
    {
455 1
        const incremented = currIdx + T.sizeof;
456

457
        // Malformed buffer?
458 1
        if(incremented > arr.length)
459 0
            throw new AnswerException(ExceptionType.FATAL_ERROR, null);
460

461 1
        auto ret = cast(T*) &arr[currIdx];
462

463 1
        currIdx = incremented;
464

465 1
        return ret;
466
    }
467

468
    A readBuff(size_t len)
469 1
    in(len >= 0)
470
    {
471 1
        const incremented = currIdx + len;
472

473
        // Malformed buffer?
474 1
        if(incremented > arr.length)
475 0
            throw new AnswerException(ExceptionType.FATAL_ERROR, null);
476

477 1
        auto ret = arr[currIdx .. incremented];
478

479 1
        currIdx = incremented;
480

481 1
        return ret;
482
    }
483
}
484

485
///
486
struct ArrayProperties
487
{
488
    OidType OID = OidType.Undefined; /// Oid
489
    int[] dimsSize; /// Dimensions sizes info
490
    size_t nElems; /// Total elements
491
    package size_t dataOffset;
492

493 1
    this(in Value cell)
494
    {
495
        try
496 1
            fillStruct(cell);
497
        catch(AnswerException e)
498
        {
499
            // Malformed array bytes buffer?
500 0
            if(e.type == ExceptionType.FATAL_ERROR && e.msg is null)
501 0
                throw new ValueConvException(
502
                    ConvExceptionType.CORRUPTED_ARRAY,
503
                    "Corrupted array",
504
                    __FILE__, __LINE__, e
505
                );
506
            else
507 0
                throw e;
508
        }
509
    }
510

511
    private void fillStruct(in Value cell)
512
    {
513 1
        auto data = BytesReader!(immutable ubyte[])(cell.data);
514

515 1
        const ArrayHeader_net* h = data.read!ArrayHeader_net;
516 1
        int nDims = bigEndianToNative!int(h.ndims);
517 1
        OID = oid2oidType(bigEndianToNative!Oid(h.OID));
518

519 1
        if(nDims < 0)
520 0
            throw new ValueConvException(ConvExceptionType.CORRUPTED_ARRAY,
521
                "Array dimensions number is negative ("~to!string(nDims)~")",
522
            );
523

524 1
        dataOffset = ArrayHeader_net.sizeof + Dim_net.sizeof * nDims;
525

526 1
        dimsSize = new int[nDims];
527

528
        // Recognize dimensions of array
529 1
        for( auto i = 0; i < nDims; ++i )
530
        {
531 1
            Dim_net* d = (cast(Dim_net*) (h + 1)) + i;
532

533 1
            const dim_size = bigEndianToNative!int(d.dim_size);
534 1
            const lbound = bigEndianToNative!int(d.lbound);
535

536 1
            if(dim_size < 0)
537 0
                throw new ValueConvException(ConvExceptionType.CORRUPTED_ARRAY,
538
                    "Dimension size is negative ("~to!string(dim_size)~")",
539
                );
540

541
            // FIXME: What is lbound in postgresql array reply?
542 1
            if(!(lbound == 1))
543 0
                throw new ValueConvException(ConvExceptionType.CORRUPTED_ARRAY,
544
                    "Please report if you came across this error! lbound=="~to!string(lbound),
545
                );
546

547 1
            dimsSize[i] = dim_size;
548

549 1
            if(i == 0) // first dimension
550 1
                nElems = dim_size;
551
            else
552 1
                nElems *= dim_size;
553
        }
554
    }
555
}
556

557
/// Represents Value as array
558
///
559
/// Actually it is a reference to the cell value of the answer table
560
immutable struct Array
561
{
562
    ArrayProperties ap; ///
563
    alias ap this;
564

565
    private ubyte[][] elements;
566
    private bool[] elementIsNULL;
567

568 1
    this(immutable Value cell)
569
    {
570 1
        if(!(cell.format == ValueFormat.BINARY))
571 0
            throw new ValueConvException(ConvExceptionType.NOT_BINARY,
572
                msg_NOT_BINARY, __FILE__, __LINE__);
573

574 1
        ap = cast(immutable) ArrayProperties(cell);
575

576
        // Looping through all elements and fill out index of them
577
        try
578
        {
579 1
            auto elements = new immutable (ubyte)[][ nElems ];
580 1
            auto elementIsNULL = new bool[ nElems ];
581

582 1
            auto data = BytesReader!(immutable ubyte[])(cell.data[ap.dataOffset .. $]);
583

584 1
            for(uint i = 0; i < nElems; ++i)
585
            {
586
                /// size in network byte order
587 1
                const size_net = data.read!(ubyte[int.sizeof]);
588

589 1
                uint size = bigEndianToNative!uint(*size_net);
590 1
                if( size == size.max ) // NULL magic number
591
                {
592 1
                    elementIsNULL[i] = true;
593
                }
594
                else
595
                {
596 1
                    elementIsNULL[i] = false;
597 1
                    elements[i] = data.readBuff(size);
598
                }
599
            }
600

601 1
            this.elements = elements.idup;
602 1
            this.elementIsNULL = elementIsNULL.idup;
603
        }
604
        catch(AnswerException e)
605
        {
606
            // Malformed array bytes buffer?
607 0
            if(e.type == ExceptionType.FATAL_ERROR && e.msg is null)
608 0
                throw new ValueConvException(
609
                    ConvExceptionType.CORRUPTED_ARRAY,
610
                    "Corrupted array",
611
                    __FILE__, __LINE__, e
612
                );
613
            else
614 0
                throw e;
615
        }
616
    }
617

618
    /// Returns number of elements in array
619
    /// Useful for one-dimensional arrays
620
    size_t length()
621
    {
622 1
        return nElems;
623
    }
624

625
    /// Returns Value struct by index
626
    /// Useful for one-dimensional arrays
627
    immutable (Value) opIndex(size_t n)
628
    {
629 0
        return opIndex(n.to!int);
630
    }
631

632
    /// Returns Value struct by index
633
    /// Useful for one-dimensional arrays
634
    immutable (Value) opIndex(int n)
635
    {
636 1
        return getValue(n);
637
    }
638

639
    /// Returns Value struct
640
    /// Useful for multidimensional arrays
641
    immutable (Value) getValue( ... )
642
    {
643 1
        auto n = coords2Serial( _argptr, _arguments );
644

645 1
        return getValueByFlatIndex(n);
646
    }
647

648
    ///
649
    package immutable (Value) getValueByFlatIndex(size_t n)
650
    {
651 1
        return immutable Value(elements[n], OID, elementIsNULL[n], ValueFormat.BINARY);
652
    }
653

654
    /// Value NULL checking
655
    bool isNULL( ... )
656
    {
657 0
        auto n = coords2Serial( _argptr, _arguments );
658 0
        return elementIsNULL[n];
659
    }
660

661
    private size_t coords2Serial( va_list _argptr, TypeInfo[] _arguments )
662
    {
663 1
        assert( _arguments.length > 0, "Number of the arguments must be more than 0" );
664

665
        // Variadic args parsing
666 1
        auto args = new int[ _arguments.length ];
667

668 1
        if(!(dimsSize.length == args.length))
669 0
            throw new ValueConvException(
670
                ConvExceptionType.OUT_OF_RANGE,
671
                "Mismatched dimensions number in Value and passed arguments: "~dimsSize.length.to!string~" and "~args.length.to!string,
672
            );
673

674 1
        for( uint i; i < args.length; ++i )
675
        {
676 1
            assert( _arguments[i] == typeid(int) );
677 1
            args[i] = va_arg!(int)(_argptr);
678

679 1
            if(!(dimsSize[i] > args[i]))
680 0
                throw new ValueConvException(
681
                    ConvExceptionType.OUT_OF_RANGE,
682
                    "Index is out of range",
683
                );
684
        }
685

686
        // Calculates serial number of the element
687 1
        auto inner = args.length - 1; // inner dimension
688 1
        auto element_num = args[inner]; // serial number of the element
689 1
        uint s = 1; // perpendicular to a vector which size is calculated currently
690 1
        for( auto i = inner; i > 0; --i )
691
        {
692 1
            s *= dimsSize[i];
693 1
            element_num += s * args[i-1];
694
        }
695

696 1
        assert( element_num <= nElems );
697 1
        return element_num;
698
    }
699
}
700

701
/// Notify
702
class Notify
703
{
704
    private immutable PGnotify* n;
705

706 0
    package this(immutable PGnotify* pgn)
707
    {
708 0
        assert(pgn != null);
709

710 0
        n = pgn;
711 0
        cast(void) enforce!OutOfMemoryError(n, "Can't write notify");
712
    }
713

714
    ~this()
715
    {
716 0
        PQfreemem( cast(void*) n );
717
    }
718

719
    /// Returns notification condition name
720 0
    string name() { return to!string( n.relname ); }
721

722
    /// Returns notification parameter
723 0
    string extra() { return to!string( n.extra ); }
724

725
    /// Returns process ID of notifying server process
726 0
    size_t pid() { return n.be_pid; }
727
}
728

729
/// Covers errors of Answer creation when data was not received due to syntax errors, etc
730
class ResponseException : Dpq2Exception
731
{
732
    immutable(Result) result;
733
    alias result this;
734

735 0
    this(immutable(Result) result, string file = __FILE__, size_t line = __LINE__)
736
    {
737 0
        this.result = result;
738

739 0
        super(result.resultErrorMessage(), file, line);
740
    }
741
}
742

743
// TODO: deprecated
744
alias AnswerCreationException = ResponseException;
745

746
/// Answer exception types
747
enum ExceptionType
748
{
749
    FATAL_ERROR, ///
750
    COLUMN_NOT_FOUND, /// Column is not found
751
    OUT_OF_RANGE, ///
752
    COPY_OUT_NOT_IMPLEMENTED = 10000, /// TODO
753
}
754

755
/// Covers errors of access to Answer data
756
class AnswerException : Dpq2Exception
757
{
758
    const ExceptionType type; /// Exception type
759

760 0
    this(ExceptionType t, string msg, string file = __FILE__, size_t line = __LINE__) pure @safe
761
    {
762 0
        type = t;
763 0
        super(msg, file, line);
764
    }
765
}
766

767
package immutable msg_NOT_BINARY = "Format of the column is not binary";
768

769
version (integration_tests)
770
void _integration_test( string connParam )
771
{
772
    import core.exception: AssertError;
773

774 0
    auto conn = new Connection(connParam);
775

776
    // Text type results testing
777
    {
778 0
        string sql_query =
779
        "select now() as time,  'abc'::text as field_name,   123,  456.78\n"~
780
        "union all\n"~
781

782
        "select now(),          'def'::text,                 456,  910.11\n"~
783
        "union all\n"~
784

785
        "select NULL,           'ijk_АБВГД'::text,           789,  12345.115345";
786

787 0
        auto e = conn.exec(sql_query);
788

789 0
        assert( e[1][2].as!PGtext == "456" );
790 0
        assert( e[2][1].as!PGtext == "ijk_АБВГД" );
791 0
        assert( !e[0].isNULL(0) );
792 0
        assert( e[2].isNULL(0) );
793 0
        assert( e.columnNum( "field_name" ) == 1 );
794 0
        assert( e[1]["field_name"].as!PGtext == "def" );
795 0
        assert(e.columnExists("field_name"));
796 0
        assert(!e.columnExists("foo"));
797
    }
798

799
    // Binary type arguments testing:
800 0
    QueryParams p;
801 0
    p.resultFormat = ValueFormat.BINARY;
802 0
    p.sqlCommand = "SELECT "~
803
        "-32761::smallint, "~
804
        "-2147483646::integer as integer_value, "~
805
        "'first line\nsecond line'::text, "~
806
        "array[[[1,  2, 3], "~
807
               "[4,  5, 6]], "~
808

809
              "[[7,  8, 9], "~
810
              "[10, 11,12]], "~
811

812
              "[[13,14,NULL], "~
813
               "[16,17,18]]]::integer[] as test_array, "~
814
        "NULL::smallint,"~
815
        "array[11,22,NULL,44]::integer[] as small_array, "~
816
        "array['1','23',NULL,'789A']::text[] as text_array, "~
817
        "array[]::text[] as empty_array";
818

819 0
    auto r = conn.execParams(p);
820

821
    {
822 0
        assert( r[0].isNULL(4) );
823 0
        assert( !r[0].isNULL(2) );
824

825 0
        assert( r.OID(3) == OidType.Int4Array );
826 0
        assert( r.isSupportedArray(3) );
827 0
        assert( !r.isSupportedArray(2) );
828 0
        assert( r[0].columnExists("test_array") );
829 0
        auto v = r[0]["test_array"];
830 0
        assert( v.isSupportedArray );
831 0
        assert( !r[0][2].isSupportedArray );
832 0
        auto a = v.asArray;
833 0
        assert( a.OID == OidType.Int4 );
834 0
        assert( a.getValue(2,1,2).as!PGinteger == 18 );
835 0
        assert( a.isNULL(2,0,2) );
836 0
        assert( !a.isNULL(2,1,2) );
837 0
        assert( r[0]["small_array"].asArray[1].as!PGinteger == 22 );
838 0
        assert( r[0]["small_array"].asArray[2].isNull );
839 0
        assert( r[0]["text_array"].asArray[2].isNull );
840 0
        assert( r.columnName(3) == "test_array" );
841 0
        assert( r[0].columnName(3) == "test_array" );
842 0
        assert( r[0]["empty_array"].asArray.nElems == 0 );
843 0
        assert( r[0]["empty_array"].asArray.dimsSize.length == 0 );
844 0
        assert( r[0]["empty_array"].asArray.length == 0 );
845 0
        assert( r[0]["text_array"].asArray.length == 4 );
846 0
        assert( r[0]["test_array"].asArray.length == 18 );
847

848
        // Access to NULL cell
849
        {
850 0
            bool isNullFlag = false;
851
            try
852 0
                cast(void) r[0][4].as!PGsmallint;
853
            catch(AssertError)
854 0
                isNullFlag = true;
855
            finally
856 0
                assert(isNullFlag);
857
        }
858

859
        // Access to NULL array element
860
        {
861 0
            bool isNullFlag = false;
862
            try
863 0
                cast(void) r[0]["small_array"].asArray[2].as!PGinteger;
864
            catch(AssertError)
865 0
                isNullFlag = true;
866
            finally
867 0
                assert(isNullFlag);
868
        }
869
    }
870

871
    // Notifies test
872
    {
873 0
        conn.exec( "listen test_notify; notify test_notify, 'test payload'" );
874 0
        auto notify = conn.getNextNotify;
875

876 0
        assert( notify.name == "test_notify" );
877 0
        assert( notify.extra == "test payload" );
878
    }
879

880
    // Async query test 1
881 0
    conn.sendQuery( "select 123; select 456; select 789" );
882 0
    while( conn.getResult() !is null ){}
883 0
    assert( conn.getResult() is null ); // removes null answer at the end
884

885
    // Async query test 2
886 0
    conn.sendQueryParams(p);
887 0
    while( conn.getResult() !is null ){}
888 0
    assert( conn.getResult() is null ); // removes null answer at the end
889

890
    {
891
        // Range test
892 0
        auto rowsRange = rangify(r);
893 0
        size_t count = 0;
894

895 0
        foreach(row; rowsRange)
896 0
            foreach(elem; rangify(row))
897 0
                count++;
898

899 0
        assert(count == 8);
900
    }
901

902
    {
903 0
        bool exceptionFlag = false;
904

905 0
        try r[0]["integer_value"].as!PGtext;
906
        catch(ValueConvException e)
907
        {
908 0
            exceptionFlag = true;
909 0
            assert(e.msg.length > 5); // error message check
910
        }
911
        finally
912 0
            assert(exceptionFlag);
913
    }
914

915
    {
916 0
        bool exceptionFlag = false;
917

918 0
        try conn.exec("WRONG SQL QUERY");
919
        catch(ResponseException e)
920
        {
921 0
            exceptionFlag = true;
922 0
            assert(e.msg.length > 20); // error message check
923

924
            version(LDC) destroy(e); // before Derelict unloads its bindings (prevents SIGSEGV)
925
        }
926
        finally
927 0
            assert(exceptionFlag);
928
    }
929

930
    {
931
        import dpq2.conv.from_d_types : toValue;
932

933 0
        conn.exec("CREATE TABLE test (num INTEGER)");
934 0
        scope (exit) conn.exec("DROP TABLE test");
935 0
        conn.prepare("test", "INSERT INTO test (num) VALUES ($1)");
936 0
        QueryParams qp;
937 0
        qp.preparedStatementName = "test";
938 0
        qp.args = new Value[1];
939 0
        foreach (i; 0..10)
940
        {
941 0
            qp.args[0] = i.toValue;
942 0
            conn.execPrepared(qp);
943
        }
944

945 0
        auto res = conn.exec("DELETE FROM test");
946 0
        assert(res.cmdTuples == "10");
947
    }
948
}

Read our documentation on viewing source code .

Loading