mirror of
https://github.com/dart-lang/sdk
synced 2024-11-05 18:22:09 +00:00
[doc] dart:ffi SQLite sample
Issue: https://github.com/dart-lang/sdk/issues/35775 Change-Id: I2ce86c554ffd6f49050cf63ead60809c08fb02e5 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/97504 Reviewed-by: Michael Thomsen <mit@google.com>
This commit is contained in:
parent
69079303cf
commit
f135a49861
17 changed files with 1865 additions and 0 deletions
7
samples/ffi/sqlite/.gitignore
vendored
Normal file
7
samples/ffi/sqlite/.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
.dart_tool
|
||||
.gdb_history
|
||||
.packages
|
||||
.vscode
|
||||
pubspec.lock
|
||||
test.db
|
||||
test.db-journal
|
41
samples/ffi/sqlite/README.md
Normal file
41
samples/ffi/sqlite/README.md
Normal file
|
@ -0,0 +1,41 @@
|
|||
# Sample code dart:ffi
|
||||
|
||||
This is an illustrative sample for how to use `dart:ffi`.
|
||||
|
||||
|
||||
## Building and Running this Sample
|
||||
|
||||
Building and running this sample is done through pub.
|
||||
Running `pub get` and `pub run test` should produce the following output.
|
||||
|
||||
```sh
|
||||
$ pub get
|
||||
Resolving dependencies... (6.8s)
|
||||
+ analyzer 0.35.4
|
||||
...
|
||||
+ yaml 2.1.15
|
||||
Downloading analyzer 0.35.4...
|
||||
Downloading kernel 0.3.14...
|
||||
Downloading front_end 0.1.14...
|
||||
Changed 47 dependencies!
|
||||
Precompiling executables... (18.0s)
|
||||
Precompiled test:test.
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
$ pub run test
|
||||
00:01 +0: test/sqlite_test.dart: sqlite integration test
|
||||
1 Chocolade chip cookie Chocolade cookie foo
|
||||
2 Ginger cookie null 42
|
||||
3 Cinnamon roll null null
|
||||
1 Chocolade chip cookie Chocolade cookie foo
|
||||
2 Ginger cookie null 42
|
||||
expected exception on accessing result data after close: The result has already been closed.
|
||||
expected this query to fail: no such column: non_existing_column (Code 1: SQL logic error)
|
||||
00:02 +3: All tests passed!
|
||||
```
|
||||
|
||||
## Tutorial
|
||||
|
||||
A tutorial walking through the code is available in [docs/sqlite-tutorial.md](docs/sqlite-tutorial.md).
|
130
samples/ffi/sqlite/docs/lib/scenario-default.svg
Normal file
130
samples/ffi/sqlite/docs/lib/scenario-default.svg
Normal file
|
@ -0,0 +1,130 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:xl="http://www.w3.org/1999/xlink" viewBox="27.846457 27.846457 426.19686 227.77166" width="426.19686" height="227.77166">
|
||||
<defs>
|
||||
<font-face font-family="Helvetica Neue" font-size="16" panose-1="2 0 5 3 0 0 0 2 0 4" units-per-em="1000" underline-position="-100" underline-thickness="50" slope="0" x-height="517" cap-height="714" ascent="951.9958" descent="-212.99744" font-weight="400">
|
||||
<font-face-src>
|
||||
<font-face-name name="HelveticaNeue"/>
|
||||
</font-face-src>
|
||||
</font-face>
|
||||
<font-face font-family="Helvetica Neue" font-size="10" panose-1="2 0 5 3 0 0 0 2 0 4" units-per-em="1000" underline-position="-100" underline-thickness="50" slope="0" x-height="517" cap-height="714" ascent="951.9958" descent="-212.99744" font-weight="400">
|
||||
<font-face-src>
|
||||
<font-face-name name="HelveticaNeue"/>
|
||||
</font-face-src>
|
||||
</font-face>
|
||||
</defs>
|
||||
<metadata> Produced by OmniGraffle 7.9.4
|
||||
<dc:date>2019-03-13 09:56:08 +0000</dc:date>
|
||||
</metadata>
|
||||
<g id="Canvas_1" fill="none" fill-opacity="1" stroke="none" stroke-dasharray="none" stroke-opacity="1">
|
||||
<title>Canvas 1</title>
|
||||
<rect fill="white" x="27.846457" y="27.846457" width="426.19686" height="227.77166"/>
|
||||
<g id="Canvas_1: Layer 1">
|
||||
<title>Layer 1</title>
|
||||
<g id="Graphic_3">
|
||||
<rect x="28.346457" y="85.03937" width="85.03937" height="141.73229" fill="white"/>
|
||||
<rect x="28.346457" y="85.03937" width="85.03937" height="141.73229" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
|
||||
<text transform="translate(33.346457 110.00952)" fill="black">
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="14.703685" y="15">Flutter </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="22.847685" y="33.448">App</tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="8.031685" y="69.896">(Imports </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="4.775685" y="88.34399">package)</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="Graphic_5">
|
||||
<rect x="368.50395" y="85.03937" width="85.03937" height="141.73229" fill="white"/>
|
||||
<rect x="368.50395" y="85.03937" width="85.03937" height="141.73229" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
|
||||
<text transform="translate(373.50395 137.45752)" fill="black">
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="14.855685" y="15">Native </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="12.927685" y="33.448">Library</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="Graphic_6">
|
||||
<rect x="311.81103" y="85.03937" width="56.692915" height="141.73229" fill="white"/>
|
||||
<rect x="311.81103" y="85.03937" width="56.692915" height="141.73229" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
|
||||
<text transform="translate(316.81103 146.68152)" fill="black">
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x=".5064575" y="15">dart:ffi</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="Graphic_7">
|
||||
<rect x="113.38583" y="85.03937" width="56.692915" height="141.73229" fill="white"/>
|
||||
<rect x="113.38583" y="85.03937" width="56.692915" height="141.73229" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
|
||||
<text transform="translate(118.38583 119.20552)" fill="black">
|
||||
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="3.9014575" y="10">Package </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="15.571457" y="22.28">API</tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="1.8614575" y="46.56">(Does not </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="7.0514575" y="58.839996">expose </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="7.7764575" y="71.119995">dart:ffi)</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="Graphic_8">
|
||||
<rect x="28.346457" y="226.77166" width="311.81103" height="28.346457" fill="white"/>
|
||||
<rect x="28.346457" y="226.77166" width="311.81103" height="28.346457" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
|
||||
<text transform="translate(33.346457 231.7209)" fill="black">
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="135.79352" y="15">Dart</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="Graphic_9">
|
||||
<rect x="340.1575" y="226.77166" width="113.38583" height="28.346457" fill="white"/>
|
||||
<rect x="340.1575" y="226.77166" width="113.38583" height="28.346457" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
|
||||
<text transform="translate(345.1575 231.7209)" fill="black">
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="23.428915" y="15">C / C++</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="Graphic_10">
|
||||
<rect x="28.346457" y="28.346457" width="85.03937" height="56.692915" fill="white"/>
|
||||
<rect x="28.346457" y="28.346457" width="85.03937" height="56.692915" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
|
||||
<text transform="translate(33.346457 38.244917)" fill="black">
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="22.847685" y="15">App </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="1.2236848" y="33.448">Developer</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="Graphic_11">
|
||||
<rect x="113.38583" y="28.346457" width="198.4252" height="56.692915" fill="white"/>
|
||||
<rect x="113.38583" y="28.346457" width="198.4252" height="56.692915" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
|
||||
<text transform="translate(118.38583 38.244917)" fill="black">
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="63.1006" y="15">Package</tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="57.9166" y="33.448">Developer</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="Graphic_12">
|
||||
<rect x="311.81103" y="28.346457" width="56.692915" height="56.692915" fill="white"/>
|
||||
<rect x="311.81103" y="28.346457" width="56.692915" height="56.692915" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
|
||||
<text transform="translate(316.81103 29.020918)" fill="black">
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="8.234457" y="15">Dart </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="11.490457" y="33.448">VM </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="4.2264575" y="51.895996">Team</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="Graphic_13">
|
||||
<rect x="255.11812" y="85.03937" width="56.692915" height="141.73229" fill="white"/>
|
||||
<rect x="255.11812" y="85.03937" width="56.692915" height="141.73229" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
|
||||
<text transform="translate(260.11812 149.76552)" fill="black">
|
||||
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="3.8064575" y="10">Bindings</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="Graphic_14">
|
||||
<rect x="368.50395" y="28.346457" width="85.03937" height="56.692915" fill="white"/>
|
||||
<rect x="368.50395" y="28.346457" width="85.03937" height="56.692915" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
|
||||
<text transform="translate(373.50395 29.020918)" fill="black">
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="14.855686" y="15">Native </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="12.927686" y="33.448">Library </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="1.2236862" y="51.895996">Developer</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="Graphic_15">
|
||||
<rect x="170.07874" y="85.03937" width="85.03937" height="141.73229" fill="white"/>
|
||||
<rect x="170.07874" y="85.03937" width="85.03937" height="141.73229" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
|
||||
<text transform="translate(175.07874 106.92552)" fill="black">
|
||||
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="18.074685" y="10">Package </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="2.8746848" y="22.28">Implementation</tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="9.559685" y="46.56">(Code which </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="7.259685" y="58.839996">converts C++ </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x=".1996848" y="71.119995">abstractions into </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="28.074685" y="83.39999">Dart </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="8.629685" y="95.67999">abstractions)</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 10 KiB |
149
samples/ffi/sqlite/docs/lib/scenario-full.svg
Normal file
149
samples/ffi/sqlite/docs/lib/scenario-full.svg
Normal file
|
@ -0,0 +1,149 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" viewBox="27.846457 27.846457 511.23623 227.77166" width="511.23623" height="227.77166">
|
||||
<defs>
|
||||
<font-face font-family="Helvetica Neue" font-size="16" panose-1="2 0 5 3 0 0 0 2 0 4" units-per-em="1000" underline-position="-100" underline-thickness="50" slope="0" x-height="517" cap-height="714" ascent="951.9958" descent="-212.99744" font-weight="400">
|
||||
<font-face-src>
|
||||
<font-face-name name="HelveticaNeue"/>
|
||||
</font-face-src>
|
||||
</font-face>
|
||||
<font-face font-family="Helvetica Neue" font-size="10" panose-1="2 0 5 3 0 0 0 2 0 4" units-per-em="1000" underline-position="-100" underline-thickness="50" slope="0" x-height="517" cap-height="714" ascent="951.9958" descent="-212.99744" font-weight="400">
|
||||
<font-face-src>
|
||||
<font-face-name name="HelveticaNeue"/>
|
||||
</font-face-src>
|
||||
</font-face>
|
||||
</defs>
|
||||
<metadata> Produced by OmniGraffle 7.9.4
|
||||
<dc:date>2019-03-13 09:53:08 +0000</dc:date>
|
||||
</metadata>
|
||||
<g id="Canvas_1" stroke-opacity="1" stroke="none" stroke-dasharray="none" fill-opacity="1" fill="none">
|
||||
<title>Canvas 1</title>
|
||||
<rect fill="white" x="27.846457" y="27.846457" width="511.23623" height="227.77166"/>
|
||||
<g id="Canvas_1: Layer 1">
|
||||
<title>Layer 1</title>
|
||||
<g id="Graphic_3">
|
||||
<rect x="28.346457" y="85.03937" width="85.03937" height="141.73229" fill="white"/>
|
||||
<rect x="28.346457" y="85.03937" width="85.03937" height="141.73229" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
|
||||
<text transform="translate(33.346457 110.00952)" fill="black">
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="14.703685" y="15">Flutter </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="22.847685" y="33.448">App</tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="8.031685" y="69.896">(Imports </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="4.775685" y="88.34399">package)</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="Graphic_5">
|
||||
<rect x="453.5433" y="85.03937" width="85.03937" height="141.73229" fill="white"/>
|
||||
<rect x="453.5433" y="85.03937" width="85.03937" height="141.73229" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
|
||||
<text transform="translate(458.5433 137.45752)" fill="black">
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="14.855685" y="15">Native </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="12.927685" y="33.448">Library</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="Graphic_6">
|
||||
<rect x="311.81103" y="85.03937" width="56.692915" height="141.73229" fill="white"/>
|
||||
<rect x="311.81103" y="85.03937" width="56.692915" height="141.73229" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
|
||||
<text transform="translate(316.81103 146.68152)" fill="black">
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x=".5064575" y="15">dart:ffi</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="Graphic_7">
|
||||
<rect x="113.38583" y="85.03937" width="56.692915" height="141.73229" fill="white"/>
|
||||
<rect x="113.38583" y="85.03937" width="56.692915" height="141.73229" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
|
||||
<text transform="translate(118.38583 119.20552)" fill="black">
|
||||
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="3.9014575" y="10">Package </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="15.571457" y="22.28">API</tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="1.8614575" y="46.56">(Does not </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="7.0514575" y="58.839996">expose </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="7.7764575" y="71.119995">dart:ffi)</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="Graphic_8">
|
||||
<rect x="28.346457" y="226.77166" width="311.81103" height="28.346457" fill="white"/>
|
||||
<rect x="28.346457" y="226.77166" width="311.81103" height="28.346457" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
|
||||
<text transform="translate(33.346457 231.7209)" fill="black">
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="135.79352" y="15">Dart</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="Graphic_9">
|
||||
<rect x="340.1575" y="226.77166" width="198.4252" height="28.346457" fill="white"/>
|
||||
<rect x="340.1575" y="226.77166" width="198.4252" height="28.346457" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
|
||||
<text transform="translate(345.1575 231.7209)" fill="black">
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="65.9486" y="15">C / C++</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="Graphic_10">
|
||||
<rect x="28.346457" y="28.346457" width="85.03937" height="56.692915" fill="white"/>
|
||||
<rect x="28.346457" y="28.346457" width="85.03937" height="56.692915" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
|
||||
<text transform="translate(33.346457 38.244917)" fill="black">
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="22.847685" y="15">App </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="1.2236848" y="33.448">Developer</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="Graphic_11">
|
||||
<rect x="113.38583" y="28.346457" width="198.4252" height="56.692915" fill="white"/>
|
||||
<rect x="113.38583" y="28.346457" width="198.4252" height="56.692915" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
|
||||
<text transform="translate(118.38583 38.244917)" fill="black">
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="63.1006" y="15">Package</tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="57.9166" y="33.448">Developer</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="Graphic_12">
|
||||
<rect x="311.81103" y="28.346457" width="56.692915" height="56.692915" fill="white"/>
|
||||
<rect x="311.81103" y="28.346457" width="56.692915" height="56.692915" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
|
||||
<text transform="translate(316.81103 29.020918)" fill="black">
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="8.234457" y="15">Dart </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="11.490457" y="33.448">VM </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="4.2264575" y="51.895996">Team</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="Graphic_13">
|
||||
<rect x="255.11812" y="85.03937" width="56.692915" height="141.73229" fill="white"/>
|
||||
<rect x="255.11812" y="85.03937" width="56.692915" height="141.73229" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
|
||||
<text transform="translate(260.11812 149.76552)" fill="black">
|
||||
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="3.8064575" y="10">Bindings</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="Graphic_14">
|
||||
<rect x="453.5433" y="28.346457" width="85.03937" height="56.692915" fill="white"/>
|
||||
<rect x="453.5433" y="28.346457" width="85.03937" height="56.692915" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
|
||||
<text transform="translate(458.5433 29.020918)" fill="black">
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="14.855686" y="15">Native </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="12.927686" y="33.448">Library </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="1.2236862" y="51.895996">Developer</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="Graphic_15">
|
||||
<rect x="170.07874" y="85.03937" width="85.03937" height="141.73229" fill="white"/>
|
||||
<rect x="170.07874" y="85.03937" width="85.03937" height="141.73229" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
|
||||
<text transform="translate(175.07874 106.92552)" fill="black">
|
||||
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="18.074685" y="10">Package </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="2.8746848" y="22.28">Implementation</tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="9.559685" y="46.56">(Code which </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="7.259685" y="58.839996">converts C++ </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x=".1996848" y="71.119995">abstractions into </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="28.074685" y="83.39999">Dart </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="8.629685" y="95.67999">abstractions)</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="Graphic_16">
|
||||
<rect x="368.50395" y="28.346457" width="85.03937" height="56.692915" fill="white"/>
|
||||
<rect x="368.50395" y="28.346457" width="85.03937" height="56.692915" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
|
||||
<text transform="translate(373.50395 38.244917)" fill="black">
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="6.407685" y="15">Package </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="16" font-weight="400" fill="black" x="1.2236848" y="33.448">Developer</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="Graphic_17">
|
||||
<rect x="368.50395" y="85.03937" width="85.03937" height="141.73229" fill="white"/>
|
||||
<rect x="368.50395" y="85.03937" width="85.03937" height="141.73229" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
|
||||
<text transform="translate(373.50395 119.20552)" fill="black">
|
||||
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="14.554685" y="10">Glue code</tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="9.559685" y="34.28">(Code which </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="8.719685" y="46.56">takes care of </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x="5.194685" y="58.839996">things such as </tspan>
|
||||
<tspan font-family="Helvetica Neue" font-size="10" font-weight="400" fill="black" x=".7796848" y="71.119995">C++ exceptions)</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 12 KiB |
234
samples/ffi/sqlite/docs/sqlite-tutorial.md
Normal file
234
samples/ffi/sqlite/docs/sqlite-tutorial.md
Normal file
|
@ -0,0 +1,234 @@
|
|||
# dart:ffi SQLite mini tutorial
|
||||
|
||||
In this mini tutorial we learn how to bind SQLite, a native library, in Dart using Dart's new foreign function interface `dart:ffi`.
|
||||
We build a package which provides a Dartlike SQLite API using objects and `Iterator`s.
|
||||
Inside the package we write Dart code which directly invokes C functions and manipulates C memory.
|
||||
|
||||
## Binding C Functions to Dart
|
||||
|
||||
The first step is to load a Native Library:
|
||||
|
||||
```dart
|
||||
import "dart:ffi";
|
||||
|
||||
DynamicLibrary sqlite = dlopenPlatformSpecific("sqlite3");
|
||||
```
|
||||
|
||||
In a `DynamicLibrary` we can `lookup` functions.
|
||||
Let's lookup the function `sqlite3_prepare_v2` in the SQLite library.
|
||||
That function has the following signature in the library header file.
|
||||
|
||||
```c++
|
||||
SQLITE_API int sqlite3_prepare_v2(
|
||||
sqlite3 *db, /* Database handle */
|
||||
const char *zSql, /* SQL statement, UTF-8 encoded */
|
||||
int nByte, /* Maximum length of zSql in bytes. */
|
||||
sqlite3_stmt **ppStmt, /* OUT: Statement handle */
|
||||
const char **pzTail /* OUT: Pointer to unused portion of zSql */
|
||||
);
|
||||
```
|
||||
|
||||
In order to lookup a function, we need a _C signature_ and a _Dart signature_.
|
||||
|
||||
```dart
|
||||
typedef sqlite3_prepare_v2_native_t = Int32 Function(
|
||||
DatabasePointer database,
|
||||
CString query,
|
||||
Int32 nbytes,
|
||||
Pointer<StatementPointer> statementOut,
|
||||
Pointer<CString> tail);
|
||||
|
||||
typedef Sqlite3_prepare_v2_t = int Function(
|
||||
DatabasePointer database,
|
||||
CString query,
|
||||
int nbytes,
|
||||
Pointer<StatementPointer> statementOut,
|
||||
Pointer<CString> tail);
|
||||
```
|
||||
|
||||
With these two signatures we can `lookup` the C function and expose it as a Dart function with `asFunction`.
|
||||
|
||||
```dart
|
||||
Sqlite3_prepare_v2_t sqlite3_prepare_v2 = sqlite
|
||||
.lookup<NativeFunction<sqlite3_prepare_v2_native_t>>("sqlite3_prepare_v2")
|
||||
.asFunction();
|
||||
```
|
||||
|
||||
Browse the code: [platform specific dynamic library loading](../lib/src/ffi/dylib_utils.dart), [C signatures](../lib/src/bindings/signatures.dart), [Dart signatures and bindings](../lib/src/bindings/bindings.dart), and [dart:ffi dynamic library interface](../../../../sdk/lib/ffi/dynamic_library.dart).
|
||||
|
||||
## Managing C Memory
|
||||
|
||||
In order to call `sqlite3_prepare_v2` to prepare a SQLite statement before executing, we need to be able to pass C pointers to C functions.
|
||||
|
||||
Database and Statement pointers are opaque pointers in the SQLite C API.
|
||||
We specify these as classes extending `Pointer<Void>`.
|
||||
|
||||
```dart
|
||||
class DatabasePointer extends Pointer<Void> {}
|
||||
class StatementPointer extends Pointer<Void> {}
|
||||
```
|
||||
|
||||
Strings in C are pointers to character arrays.
|
||||
|
||||
```dart
|
||||
class CString extends Pointer<Int8> {}
|
||||
```
|
||||
|
||||
Pointers to C integers, floats, an doubles can be read from and written through to `dart:ffi`.
|
||||
However, before we can write to C memory from dart, we need to `allocate` some memory.
|
||||
|
||||
```dart
|
||||
Pointer<Int8> p = allocate(); // Infers type argument allocate<Int8>(), and allocates 1 byte.
|
||||
p.store(123); // Stores a Dart int into this C int8.
|
||||
int v = p.load(); // Infers type argument p.load<int>(), and loads a value from C memory.
|
||||
```
|
||||
|
||||
Note that you can only load a Dart `int` from a C `Int8`.
|
||||
Trying to load a Dart `double` will result in a runtime exception.
|
||||
|
||||
We've almost modeled C Strings.
|
||||
The last thing we need is to use this `Pointer` as an array.
|
||||
We can do this by using `elementAt`.
|
||||
|
||||
```dart
|
||||
CString string = allocate(count: 4).cast(); // Allocates 4 bytes and casts it to a string.
|
||||
string.store(73); // Stores 'F' at index 0.
|
||||
string.elementAt(1).store(73); // Stores 'F' at index 1.
|
||||
string.elementAt(2).store(70); // Stores 'I' at index 2.
|
||||
string.elementAt(3).store(0); // Null terminates the string.
|
||||
```
|
||||
|
||||
We wrap the above logic of allocating strings in the constructor `CString.allocate`.
|
||||
|
||||
Now we have all ingredients to call `sqlite3_prepare_v2`.
|
||||
|
||||
```dart
|
||||
Pointer<StatementPointer> statementOut = allocate();
|
||||
CString queryC = CString.allocate(query);
|
||||
int resultCode = sqlite3_prepare_v2(
|
||||
_database, queryC, -1, statementOut, fromAddress(0));
|
||||
```
|
||||
|
||||
With `dart:ffi` we are responsible for freeing C memory that we allocate.
|
||||
So after calling `sqlite3_prepare_v2` we read out the statement pointer, and free the statement pointer pointer and `CString` which held the query string.
|
||||
|
||||
```
|
||||
StatementPointer statement = statementOut.load();
|
||||
statementOut.free();
|
||||
queryC.free();
|
||||
```
|
||||
|
||||
Browse the code: [CString class](../lib/src/ffi/cstring.dart), [code calling sqlite3_prepare_v2](../lib/src/database.dart#57), and [dart:ffi pointer interface](../../../../sdk/lib/ffi/ffi.dart).
|
||||
|
||||
## Dart API
|
||||
|
||||
We would like to present the users of our package with an object oriented API - not exposing any `dart:ffi` objects to them.
|
||||
|
||||
The SQLite C API returns a cursor to the first row of a result after executing a query.
|
||||
We can read out the columns of this row and move the cursor to the next row.
|
||||
The most natural way to expose this in Dart is through an `Iterable`.
|
||||
We provide our package users with the following API.
|
||||
|
||||
```dart
|
||||
class Result implements Iterable<Row> {}
|
||||
|
||||
class Row {
|
||||
dynamic readColumnByIndex(int columnIndex) {}
|
||||
dynamic readColumn(String columnName) {}
|
||||
}
|
||||
```
|
||||
|
||||
However, this interface does not completely match the semantics of the C API.
|
||||
When we start reading the next `Row`, we do no longer have access to the previous `Row`.
|
||||
We can model this by letting a `Row` keep track if its current or not.
|
||||
|
||||
```dart
|
||||
class Row {
|
||||
bool _isCurrentRow = true;
|
||||
|
||||
dynamic readColumnByIndex(int columnIndex) {
|
||||
if (!_isCurrentRow) {
|
||||
throw Exception(
|
||||
"This row is not the current row, reading data from the non-current"
|
||||
" row is not supported by sqlite.");
|
||||
}
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
A second mismatch between Dart and C is that in C we have to manually release resources.
|
||||
After executing a query and reading its results we need to call `sqlite3_finalize(statement)`.
|
||||
|
||||
We can take two approaches here, either we structure the API in such a way that users of our package (implicitly) release resources, or we use finalizers to release resources.
|
||||
In this tutorial we take the first approach.
|
||||
|
||||
If our users iterate over all `Row`s, we can implicitly finalize the statement after they are done with the last row.
|
||||
However, if they decide they do not want to iterate over the whole result, they need to explicitly state this.
|
||||
In this tutorial, we use the `ClosableIterator` abstraction for `Iterators` with backing resources that need to be `close`d.
|
||||
|
||||
```dart
|
||||
Result result = d.query("""
|
||||
select id, name
|
||||
from Cookies
|
||||
;""");
|
||||
for (Row r in result) {
|
||||
String name = r.readColumn("name");
|
||||
print(name);
|
||||
}
|
||||
// Implicitly closes the iterator.
|
||||
|
||||
result = d.query("""
|
||||
select id, name
|
||||
from Cookies
|
||||
;""");
|
||||
for (Row r in result) {
|
||||
int id = r.readColumn("id");
|
||||
if (id == 1) {
|
||||
result.close(); // Explicitly closes the iterator, releasing underlying resources.
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Browse the code: [Database, Result, Row](../lib/src/database.dart), and [CloseableIterator](../lib/src/collections/closable_iterator.dart).
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
The following diagram summarized what we have implemented as _package developers_ in this tutorial.
|
||||
|
||||
![architecture](lib/scenario-default.svg)
|
||||
|
||||
As the package developers wrapping an existing native library, we have only written Dart code - not any C/C++ code.
|
||||
We specified bindings to the native library.
|
||||
We have provided our package users with an object oriented API without exposing any `dart:ffi` objects.
|
||||
And finally, we have implemented the package API by calling the C API.
|
||||
|
||||
## Current dart:ffi Development Status
|
||||
|
||||
In this minitutorial we used these `dart:ffi` features:
|
||||
|
||||
* Loading dynamic libararies and looking up C functions in these dynamic libraries.
|
||||
* Calling C functions, with `dart:ffi` automatically marshalling arguments and return value.
|
||||
* Manipulating C memory through `Pointer`s with `allocate`, `free`, `load`, `store`, and `elementAt`.
|
||||
|
||||
Features which we did not use in this tutorial:
|
||||
|
||||
* `@struct` on subtypes of `Pointer` to define a struct with fields. (However, this feature is likely to change in the future.)
|
||||
|
||||
Features which `dart:ffi` does not support yet:
|
||||
|
||||
* Callbacks from C back into Dart.
|
||||
* Finalizers
|
||||
* C++ Exceptions (Not on roadmap yet.)
|
||||
|
||||
Platform limitations:
|
||||
|
||||
* `dart:ffi` is only enabled on 64 bit Windows, Linux, and MacOS. (Arm64 and 32 bit Intel are under review.)
|
||||
* `dart:ffi` only works in JIT mode, not in AOT.
|
||||
|
||||
It is possible to work around some of the current limitations by adding a C/C++ layer.
|
||||
For example we could catch C++ exceptions in a C++ layer, and rethrow them in Dart.
|
||||
The architecture diagram would change to the following in that case.
|
||||
|
||||
![architecture2](lib/scenario-full.svg)
|
6
samples/ffi/sqlite/lib/sqlite.dart
Normal file
6
samples/ffi/sqlite/lib/sqlite.dart
Normal file
|
@ -0,0 +1,6 @@
|
|||
/// A synchronous SQLite wrapper.
|
||||
///
|
||||
/// Written using dart:ffi.
|
||||
library sqlite;
|
||||
|
||||
export "src/database.dart";
|
388
samples/ffi/sqlite/lib/src/bindings/bindings.dart
Normal file
388
samples/ffi/sqlite/lib/src/bindings/bindings.dart
Normal file
|
@ -0,0 +1,388 @@
|
|||
import "dart:ffi";
|
||||
|
||||
import "../ffi/cstring.dart";
|
||||
import "../ffi/dylib_utils.dart";
|
||||
|
||||
import "signatures.dart";
|
||||
import "types.dart";
|
||||
|
||||
class _SQLiteBindings {
|
||||
DynamicLibrary sqlite;
|
||||
|
||||
/// Opening A New Database Connection
|
||||
///
|
||||
/// ^These routines open an SQLite database file as specified by the
|
||||
/// filename argument. ^The filename argument is interpreted as UTF-8 for
|
||||
/// sqlite3_open() and sqlite3_open_v2() and as UTF-16 in the native byte
|
||||
/// order for sqlite3_open16(). ^(A database connection handle is usually
|
||||
/// returned in *ppDb, even if an error occurs. The only exception is that
|
||||
/// if SQLite is unable to allocate memory to hold the sqlite3 object,
|
||||
/// a NULL will be written into *ppDb instead of a pointer to the sqlite3
|
||||
/// object.)^ ^(If the database is opened (and/or created) successfully, then
|
||||
/// [SQLITE_OK] is returned. Otherwise an error code is returned.)^ ^The
|
||||
/// [sqlite3_errmsg] or sqlite3_errmsg16() routines can be used to obtain
|
||||
/// an English language description of the error following a failure of any
|
||||
/// of the sqlite3_open() routines.
|
||||
int Function(CString filename, Pointer<DatabasePointer> databaseOut,
|
||||
int flags, CString vfs) sqlite3_open_v2;
|
||||
|
||||
int Function(DatabasePointer database) sqlite3_close_v2;
|
||||
|
||||
/// Compiling An SQL Statement
|
||||
///
|
||||
/// To execute an SQL query, it must first be compiled into a byte-code
|
||||
/// program using one of these routines.
|
||||
///
|
||||
/// The first argument, "db", is a database connection obtained from a
|
||||
/// prior successful call to sqlite3_open, [sqlite3_open_v2] or
|
||||
/// sqlite3_open16. The database connection must not have been closed.
|
||||
///
|
||||
/// The second argument, "zSql", is the statement to be compiled, encoded
|
||||
/// as either UTF-8 or UTF-16. The sqlite3_prepare() and sqlite3_prepare_v2()
|
||||
/// interfaces use UTF-8, and sqlite3_prepare16() and sqlite3_prepare16_v2()
|
||||
/// use UTF-16.
|
||||
///
|
||||
/// ^If the nByte argument is less than zero, then zSql is read up to the
|
||||
/// first zero terminator. ^If nByte is non-negative, then it is the maximum
|
||||
/// number of bytes read from zSql. ^When nByte is non-negative, the
|
||||
/// zSql string ends at either the first '\000' or '\u0000' character or
|
||||
/// the nByte-th byte, whichever comes first. If the caller knows
|
||||
/// that the supplied string is nul-terminated, then there is a small
|
||||
/// performance advantage to be gained by passing an nByte parameter that
|
||||
/// is equal to the number of bytes in the input string <i>including</i>
|
||||
/// the nul-terminator bytes.
|
||||
///
|
||||
/// ^If pzTail is not NULL then *pzTail is made to point to the first byte
|
||||
/// past the end of the first SQL statement in zSql. These routines only
|
||||
/// compile the first statement in zSql, so *pzTail is left pointing to
|
||||
/// what remains uncompiled.
|
||||
///
|
||||
/// ^*ppStmt is left pointing to a compiled prepared statement that can be
|
||||
/// executed using sqlite3_step. ^If there is an error, *ppStmt is set
|
||||
/// to NULL. ^If the input text contains no SQL (if the input is an empty
|
||||
/// string or a comment) then *ppStmt is set to NULL.
|
||||
/// The calling procedure is responsible for deleting the compiled
|
||||
/// SQL statement using [sqlite3_finalize] after it has finished with it.
|
||||
/// ppStmt may not be NULL.
|
||||
///
|
||||
/// ^On success, the sqlite3_prepare family of routines return [SQLITE_OK];
|
||||
/// otherwise an error code is returned.
|
||||
///
|
||||
/// The sqlite3_prepare_v2() and sqlite3_prepare16_v2() interfaces are
|
||||
/// recommended for all new programs. The two older interfaces are retained
|
||||
/// for backwards compatibility, but their use is discouraged.
|
||||
/// ^In the "v2" interfaces, the prepared statement
|
||||
/// that is returned (the sqlite3_stmt object) contains a copy of the
|
||||
/// original SQL text. This causes the [sqlite3_step] interface to
|
||||
/// behave differently in three ways:
|
||||
int Function(
|
||||
DatabasePointer database,
|
||||
CString query,
|
||||
int nbytes,
|
||||
Pointer<StatementPointer> statementOut,
|
||||
Pointer<CString> tail) sqlite3_prepare_v2;
|
||||
|
||||
/// Evaluate An SQL Statement
|
||||
///
|
||||
/// After a prepared statement has been prepared using either
|
||||
/// [sqlite3_prepare_v2] or sqlite3_prepare16_v2() or one of the legacy
|
||||
/// interfaces sqlite3_prepare() or sqlite3_prepare16(), this function
|
||||
/// must be called one or more times to evaluate the statement.
|
||||
///
|
||||
/// The details of the behavior of the sqlite3_step() interface depend
|
||||
/// on whether the statement was prepared using the newer "v2" interface
|
||||
/// [sqlite3_prepare_v2] and sqlite3_prepare16_v2() or the older legacy
|
||||
/// interface sqlite3_prepare() and sqlite3_prepare16(). The use of the
|
||||
/// new "v2" interface is recommended for new applications but the legacy
|
||||
/// interface will continue to be supported.
|
||||
///
|
||||
/// ^In the legacy interface, the return value will be either [SQLITE_BUSY],
|
||||
/// [SQLITE_DONE], [SQLITE_ROW], [SQLITE_ERROR], or [SQLITE_MISUSE].
|
||||
/// ^With the "v2" interface, any of the other [result codes] or
|
||||
/// [extended result codes] might be returned as well.
|
||||
///
|
||||
/// ^[SQLITE_BUSY] means that the database engine was unable to acquire the
|
||||
/// database locks it needs to do its job. ^If the statement is a [COMMIT]
|
||||
/// or occurs outside of an explicit transaction, then you can retry the
|
||||
/// statement. If the statement is not a [COMMIT] and occurs within an
|
||||
/// explicit transaction then you should rollback the transaction before
|
||||
/// continuing.
|
||||
///
|
||||
/// ^[SQLITE_DONE] means that the statement has finished executing
|
||||
/// successfully. sqlite3_step() should not be called again on this virtual
|
||||
/// machine without first calling [sqlite3_reset()] to reset the virtual
|
||||
/// machine back to its initial state.
|
||||
///
|
||||
/// ^If the SQL statement being executed returns any data, then [SQLITE_ROW]
|
||||
/// is returned each time a new row of data is ready for processing by the
|
||||
/// caller. The values may be accessed using the [column access functions].
|
||||
/// sqlite3_step() is called again to retrieve the next row of data.
|
||||
///
|
||||
/// ^[SQLITE_ERROR] means that a run-time error (such as a constraint
|
||||
/// violation) has occurred. sqlite3_step() should not be called again on
|
||||
/// the VM. More information may be found by calling [sqlite3_errmsg()].
|
||||
/// ^With the legacy interface, a more specific error code (for example,
|
||||
/// [SQLITE_INTERRUPT], [SQLITE_SCHEMA], [SQLITE_CORRUPT], and so forth)
|
||||
/// can be obtained by calling [sqlite3_reset()] on the
|
||||
/// prepared statement. ^In the "v2" interface,
|
||||
/// the more specific error code is returned directly by sqlite3_step().
|
||||
///
|
||||
/// [SQLITE_MISUSE] means that the this routine was called inappropriately.
|
||||
/// Perhaps it was called on a prepared statement that has
|
||||
/// already been [sqlite3_finalize | finalized] or on one that had
|
||||
/// previously returned [SQLITE_ERROR] or [SQLITE_DONE]. Or it could
|
||||
/// be the case that the same database connection is being used by two or
|
||||
/// more threads at the same moment in time.
|
||||
///
|
||||
/// For all versions of SQLite up to and including 3.6.23.1, a call to
|
||||
/// [sqlite3_reset] was required after sqlite3_step() returned anything
|
||||
/// other than [Errors.SQLITE_ROW] before any subsequent invocation of
|
||||
/// sqlite3_step(). Failure to reset the prepared statement using
|
||||
/// [sqlite3_reset()] would result in an [Errors.SQLITE_MISUSE] return from
|
||||
/// sqlite3_step(). But after version 3.6.23.1, sqlite3_step() began
|
||||
/// calling [sqlite3_reset] automatically in this circumstance rather
|
||||
/// than returning [Errors.SQLITE_MISUSE]. This is not considered a
|
||||
/// compatibility break because any application that ever receives an
|
||||
/// [Errors.SQLITE_MISUSE] error is broken by definition. The
|
||||
/// [SQLITE_OMIT_AUTORESET] compile-time option
|
||||
/// can be used to restore the legacy behavior.
|
||||
///
|
||||
/// <b>Goofy Interface Alert:</b> In the legacy interface, the sqlite3_step()
|
||||
/// API always returns a generic error code, [SQLITE_ERROR], following any
|
||||
/// error other than [SQLITE_BUSY] and [SQLITE_MISUSE]. You must call
|
||||
/// [sqlite3_reset()] or [sqlite3_finalize()] in order to find one of the
|
||||
/// specific [error codes] that better describes the error.
|
||||
/// We admit that this is a goofy design. The problem has been fixed
|
||||
/// with the "v2" interface. If you prepare all of your SQL statements
|
||||
/// using either [sqlite3_prepare_v2()] or [sqlite3_prepare16_v2()] instead
|
||||
/// of the legacy [sqlite3_prepare()] and [sqlite3_prepare16()] interfaces,
|
||||
/// then the more specific [error codes] are returned directly
|
||||
/// by sqlite3_step(). The use of the "v2" interface is recommended.
|
||||
int Function(StatementPointer statement) sqlite3_step;
|
||||
|
||||
/// CAPI3REF: Reset A Prepared Statement Object
|
||||
///
|
||||
/// The sqlite3_reset() function is called to reset a prepared statement
|
||||
/// object back to its initial state, ready to be re-executed.
|
||||
/// ^Any SQL statement variables that had values bound to them using
|
||||
/// the sqlite3_bind_blob | sqlite3_bind_*() API retain their values.
|
||||
/// Use sqlite3_clear_bindings() to reset the bindings.
|
||||
///
|
||||
/// ^The [sqlite3_reset] interface resets the prepared statement S
|
||||
/// back to the beginning of its program.
|
||||
///
|
||||
/// ^If the most recent call to [sqlite3_step] for the
|
||||
/// prepared statement S returned [Errors.SQLITE_ROW] or [Errors.SQLITE_DONE],
|
||||
/// or if [sqlite3_step] has never before been called on S,
|
||||
/// then [sqlite3_reset] returns [Errors.SQLITE_OK].
|
||||
///
|
||||
/// ^If the most recent call to [sqlite3_step(S)] for the
|
||||
/// prepared statement S indicated an error, then
|
||||
/// [sqlite3_reset] returns an appropriate [Errors].
|
||||
///
|
||||
/// ^The [sqlite3_reset] interface does not change the values
|
||||
int Function(StatementPointer statement) sqlite3_reset;
|
||||
|
||||
/// Destroy A Prepared Statement Object
|
||||
///
|
||||
/// ^The sqlite3_finalize() function is called to delete a prepared statement.
|
||||
/// ^If the most recent evaluation of the statement encountered no errors
|
||||
/// or if the statement is never been evaluated, then sqlite3_finalize()
|
||||
/// returns SQLITE_OK. ^If the most recent evaluation of statement S failed,
|
||||
/// then sqlite3_finalize(S) returns the appropriate error code or extended
|
||||
/// error code.
|
||||
///
|
||||
/// ^The sqlite3_finalize(S) routine can be called at any point during
|
||||
/// the life cycle of prepared statement S:
|
||||
/// before statement S is ever evaluated, after
|
||||
/// one or more calls to [sqlite3_reset], or after any call
|
||||
/// to [sqlite3_step] regardless of whether or not the statement has
|
||||
/// completed execution.
|
||||
///
|
||||
/// ^Invoking sqlite3_finalize() on a NULL pointer is a harmless no-op.
|
||||
///
|
||||
/// The application must finalize every prepared statement in order to avoid
|
||||
/// resource leaks. It is a grievous error for the application to try to use
|
||||
/// a prepared statement after it has been finalized. Any use of a prepared
|
||||
/// statement after it has been finalized can result in undefined and
|
||||
/// undesirable behavior such as segfaults and heap corruption.
|
||||
int Function(StatementPointer statement) sqlite3_finalize;
|
||||
|
||||
/// Number Of Columns In A Result Set
|
||||
///
|
||||
/// ^Return the number of columns in the result set returned by the
|
||||
/// prepared statement. ^This routine returns 0 if pStmt is an SQL
|
||||
/// statement that does not return data (for example an [UPDATE]).
|
||||
int Function(StatementPointer statement) sqlite3_column_count;
|
||||
|
||||
/// Column Names In A Result Set
|
||||
///
|
||||
/// ^These routines return the name assigned to a particular column
|
||||
/// in the result set of a SELECT statement. ^The sqlite3_column_name()
|
||||
/// interface returns a pointer to a zero-terminated UTF-8 string
|
||||
/// and sqlite3_column_name16() returns a pointer to a zero-terminated
|
||||
/// UTF-16 string. ^The first parameter is the prepared statement
|
||||
/// that implements the SELECT statement. ^The second parameter is the
|
||||
/// column number. ^The leftmost column is number 0.
|
||||
///
|
||||
/// ^The returned string pointer is valid until either the prepared statement
|
||||
/// is destroyed by [sqlite3_finalize] or until the statement is automatically
|
||||
/// reprepared by the first call to [sqlite3_step] for a particular run
|
||||
/// or until the next call to
|
||||
/// sqlite3_column_name() or sqlite3_column_name16() on the same column.
|
||||
///
|
||||
/// ^If sqlite3_malloc() fails during the processing of either routine
|
||||
/// (for example during a conversion from UTF-8 to UTF-16) then a
|
||||
/// NULL pointer is returned.
|
||||
///
|
||||
/// ^The name of a result column is the value of the "AS" clause for
|
||||
/// that column, if there is an AS clause. If there is no AS clause
|
||||
/// then the name of the column is unspecified and may change from
|
||||
CString Function(StatementPointer statement, int columnIndex)
|
||||
sqlite3_column_name;
|
||||
|
||||
/// CAPI3REF: Declared Datatype Of A Query Result
|
||||
///
|
||||
/// ^(The first parameter is a prepared statement.
|
||||
/// If this statement is a SELECT statement and the Nth column of the
|
||||
/// returned result set of that SELECT is a table column (not an
|
||||
/// expression or subquery) then the declared type of the table
|
||||
/// column is returned.)^ ^If the Nth column of the result set is an
|
||||
/// expression or subquery, then a NULL pointer is returned.
|
||||
/// ^The returned string is always UTF-8 encoded.
|
||||
///
|
||||
/// ^(For example, given the database schema:
|
||||
///
|
||||
/// CREATE TABLE t1(c1 VARIANT);
|
||||
///
|
||||
/// and the following statement to be compiled:
|
||||
///
|
||||
/// SELECT c1 + 1, c1 FROM t1;
|
||||
///
|
||||
/// this routine would return the string "VARIANT" for the second result
|
||||
/// column (i==1), and a NULL pointer for the first result column (i==0).)^
|
||||
///
|
||||
/// ^SQLite uses dynamic run-time typing. ^So just because a column
|
||||
/// is declared to contain a particular type does not mean that the
|
||||
/// data stored in that column is of the declared type. SQLite is
|
||||
/// strongly typed, but the typing is dynamic not static. ^Type
|
||||
/// is associated with individual values, not with the containers
|
||||
/// used to hold those values.
|
||||
CString Function(StatementPointer statement, int columnIndex)
|
||||
sqlite3_column_decltype;
|
||||
|
||||
int Function(StatementPointer statement, int columnIndex) sqlite3_column_type;
|
||||
|
||||
ValuePointer Function(StatementPointer statement, int columnIndex)
|
||||
sqlite3_column_value;
|
||||
|
||||
double Function(StatementPointer statement, int columnIndex)
|
||||
sqlite3_column_double;
|
||||
|
||||
int Function(StatementPointer statement, int columnIndex) sqlite3_column_int;
|
||||
|
||||
CString Function(StatementPointer statement, int columnIndex)
|
||||
sqlite3_column_text;
|
||||
|
||||
/// The sqlite3_errstr() interface returns the English-language text that
|
||||
/// describes the result code, as UTF-8. Memory to hold the error message
|
||||
/// string is managed internally and must not be freed by the application.
|
||||
CString Function(int code) sqlite3_errstr;
|
||||
|
||||
/// Error Codes And Messages
|
||||
///
|
||||
/// ^The sqlite3_errcode() interface returns the numeric [result code] or
|
||||
/// [extended result code] for the most recent failed sqlite3_* API call
|
||||
/// associated with a [database connection]. If a prior API call failed
|
||||
/// but the most recent API call succeeded, the return value from
|
||||
/// sqlite3_errcode() is undefined. ^The sqlite3_extended_errcode()
|
||||
/// interface is the same except that it always returns the
|
||||
/// [extended result code] even when extended result codes are
|
||||
/// disabled.
|
||||
///
|
||||
/// ^The sqlite3_errmsg() and sqlite3_errmsg16() return English-language
|
||||
/// text that describes the error, as either UTF-8 or UTF-16 respectively.
|
||||
/// ^(Memory to hold the error message string is managed internally.
|
||||
/// The application does not need to worry about freeing the result.
|
||||
/// However, the error string might be overwritten or deallocated by
|
||||
/// subsequent calls to other SQLite interface functions.)^
|
||||
///
|
||||
/// When the serialized [threading mode] is in use, it might be the
|
||||
/// case that a second error occurs on a separate thread in between
|
||||
/// the time of the first error and the call to these interfaces.
|
||||
/// When that happens, the second error will be reported since these
|
||||
/// interfaces always report the most recent result. To avoid
|
||||
/// this, each thread can obtain exclusive use of the [database connection] D
|
||||
/// by invoking [sqlite3_mutex_enter]([sqlite3_db_mutex](D)) before beginning
|
||||
/// to use D and invoking [sqlite3_mutex_leave]([sqlite3_db_mutex](D)) after
|
||||
/// all calls to the interfaces listed here are completed.
|
||||
///
|
||||
/// If an interface fails with SQLITE_MISUSE, that means the interface
|
||||
/// was invoked incorrectly by the application. In that case, the
|
||||
/// error code and message may or may not be set.
|
||||
CString Function(DatabasePointer database) sqlite3_errmsg;
|
||||
|
||||
_SQLiteBindings() {
|
||||
sqlite = dlopenPlatformSpecific("sqlite3");
|
||||
sqlite3_open_v2 = sqlite
|
||||
.lookup<NativeFunction<sqlite3_open_v2_native_t>>("sqlite3_open_v2")
|
||||
.asFunction();
|
||||
sqlite3_close_v2 = sqlite
|
||||
.lookup<NativeFunction<sqlite3_close_v2_native_t>>("sqlite3_close_v2")
|
||||
.asFunction();
|
||||
sqlite3_prepare_v2 = sqlite
|
||||
.lookup<NativeFunction<sqlite3_prepare_v2_native_t>>(
|
||||
"sqlite3_prepare_v2")
|
||||
.asFunction();
|
||||
sqlite3_step = sqlite
|
||||
.lookup<NativeFunction<sqlite3_step_native_t>>("sqlite3_step")
|
||||
.asFunction();
|
||||
sqlite3_reset = sqlite
|
||||
.lookup<NativeFunction<sqlite3_reset_native_t>>("sqlite3_reset")
|
||||
.asFunction();
|
||||
sqlite3_finalize = sqlite
|
||||
.lookup<NativeFunction<sqlite3_finalize_native_t>>("sqlite3_finalize")
|
||||
.asFunction();
|
||||
sqlite3_errstr = sqlite
|
||||
.lookup<NativeFunction<sqlite3_errstr_native_t>>("sqlite3_errstr")
|
||||
.asFunction();
|
||||
sqlite3_errmsg = sqlite
|
||||
.lookup<NativeFunction<sqlite3_errmsg_native_t>>("sqlite3_errmsg")
|
||||
.asFunction();
|
||||
sqlite3_column_count = sqlite
|
||||
.lookup<NativeFunction<sqlite3_column_count_native_t>>(
|
||||
"sqlite3_column_count")
|
||||
.asFunction();
|
||||
sqlite3_column_name = sqlite
|
||||
.lookup<NativeFunction<sqlite3_column_name_native_t>>(
|
||||
"sqlite3_column_name")
|
||||
.asFunction();
|
||||
sqlite3_column_decltype = sqlite
|
||||
.lookup<NativeFunction<sqlite3_column_decltype_native_t>>(
|
||||
"sqlite3_column_decltype")
|
||||
.asFunction();
|
||||
sqlite3_column_type = sqlite
|
||||
.lookup<NativeFunction<sqlite3_column_type_native_t>>(
|
||||
"sqlite3_column_type")
|
||||
.asFunction();
|
||||
sqlite3_column_value = sqlite
|
||||
.lookup<NativeFunction<sqlite3_column_value_native_t>>(
|
||||
"sqlite3_column_value")
|
||||
.asFunction();
|
||||
sqlite3_column_double = sqlite
|
||||
.lookup<NativeFunction<sqlite3_column_double_native_t>>(
|
||||
"sqlite3_column_double")
|
||||
.asFunction();
|
||||
sqlite3_column_int = sqlite
|
||||
.lookup<NativeFunction<sqlite3_column_int_native_t>>(
|
||||
"sqlite3_column_int")
|
||||
.asFunction();
|
||||
sqlite3_column_text = sqlite
|
||||
.lookup<NativeFunction<sqlite3_column_text_native_t>>(
|
||||
"sqlite3_column_text")
|
||||
.asFunction();
|
||||
}
|
||||
}
|
||||
|
||||
_SQLiteBindings _cachedBindings;
|
||||
_SQLiteBindings get bindings => _cachedBindings ??= _SQLiteBindings();
|
178
samples/ffi/sqlite/lib/src/bindings/constants.dart
Normal file
178
samples/ffi/sqlite/lib/src/bindings/constants.dart
Normal file
|
@ -0,0 +1,178 @@
|
|||
/// Result Codes
|
||||
///
|
||||
/// Many SQLite functions return an integer result code from the set shown
|
||||
/// here in order to indicates success or failure.
|
||||
///
|
||||
/// New error codes may be added in future versions of SQLite.
|
||||
///
|
||||
/// See also: SQLITE_IOERR_READ | extended result codes,
|
||||
/// sqlite3_vtab_on_conflict() SQLITE_ROLLBACK | result codes.
|
||||
class Errors {
|
||||
/// Successful result
|
||||
static const int SQLITE_OK = 0;
|
||||
|
||||
/// Generic error
|
||||
static const int SQLITE_ERROR = 1;
|
||||
|
||||
/// Internal logic error in SQLite
|
||||
static const int SQLITE_INTERNAL = 2;
|
||||
|
||||
/// Access permission denied
|
||||
static const int SQLITE_PERM = 3;
|
||||
|
||||
/// Callback routine requested an abort
|
||||
static const int SQLITE_ABORT = 4;
|
||||
|
||||
/// The database file is locked
|
||||
static const int SQLITE_BUSY = 5;
|
||||
|
||||
/// A table in the database is locked
|
||||
static const int SQLITE_LOCKED = 6;
|
||||
|
||||
/// A malloc() failed
|
||||
static const int SQLITE_NOMEM = 7;
|
||||
|
||||
/// Attempt to write a readonly database
|
||||
static const int SQLITE_READONLY = 8;
|
||||
|
||||
/// Operation terminated by sqlite3_interrupt()
|
||||
static const int SQLITE_INTERRUPT = 9;
|
||||
|
||||
/// Some kind of disk I/O error occurred
|
||||
static const int SQLITE_IOERR = 10;
|
||||
|
||||
/// The database disk image is malformed
|
||||
static const int SQLITE_CORRUPT = 11;
|
||||
|
||||
/// Unknown opcode in sqlite3_file_control()
|
||||
static const int SQLITE_NOTFOUND = 12;
|
||||
|
||||
/// Insertion failed because database is full
|
||||
static const int SQLITE_FULL = 13;
|
||||
|
||||
/// Unable to open the database file
|
||||
static const int SQLITE_CANTOPEN = 14;
|
||||
|
||||
/// Database lock protocol error
|
||||
static const int SQLITE_PROTOCOL = 15;
|
||||
|
||||
/// Internal use only
|
||||
static const int SQLITE_EMPTY = 16;
|
||||
|
||||
/// The database schema changed
|
||||
static const int SQLITE_SCHEMA = 17;
|
||||
|
||||
/// String or BLOB exceeds size limit
|
||||
static const int SQLITE_TOOBIG = 18;
|
||||
|
||||
/// Abort due to constraint violation
|
||||
static const int SQLITE_CONSTRAINT = 19;
|
||||
|
||||
/// Data type mismatch
|
||||
static const int SQLITE_MISMATCH = 20;
|
||||
|
||||
/// Library used incorrectly
|
||||
static const int SQLITE_MISUSE = 21;
|
||||
|
||||
/// Uses OS features not supported on host
|
||||
static const int SQLITE_NOLFS = 22;
|
||||
|
||||
/// Authorization denied
|
||||
static const int SQLITE_AUTH = 23;
|
||||
|
||||
/// Not used
|
||||
static const int SQLITE_FORMAT = 24;
|
||||
|
||||
/// 2nd parameter to sqlite3_bind out of range
|
||||
static const int SQLITE_RANGE = 25;
|
||||
|
||||
/// File opened that is not a database file
|
||||
static const int SQLITE_NOTADB = 26;
|
||||
|
||||
/// Notifications from sqlite3_log()
|
||||
static const int SQLITE_NOTICE = 27;
|
||||
|
||||
/// Warnings from sqlite3_log()
|
||||
static const int SQLITE_WARNING = 28;
|
||||
|
||||
/// sqlite3_step() has another row ready
|
||||
static const int SQLITE_ROW = 100;
|
||||
|
||||
/// sqlite3_step() has finished executing
|
||||
static const int SQLITE_DONE = 101;
|
||||
}
|
||||
|
||||
/// Flags For File Open Operations
|
||||
///
|
||||
/// These bit values are intended for use in the
|
||||
/// 3rd parameter to the [sqlite3_open_v2()] interface and
|
||||
/// in the 4th parameter to the [sqlite3_vfs.xOpen] method.
|
||||
class Flags {
|
||||
/// Ok for sqlite3_open_v2()
|
||||
static const int SQLITE_OPEN_READONLY = 0x00000001;
|
||||
|
||||
/// Ok for sqlite3_open_v2()
|
||||
static const int SQLITE_OPEN_READWRITE = 0x00000002;
|
||||
|
||||
/// Ok for sqlite3_open_v2()
|
||||
static const int SQLITE_OPEN_CREATE = 0x00000004;
|
||||
|
||||
/// VFS only
|
||||
static const int SQLITE_OPEN_DELETEONCLOSE = 0x00000008;
|
||||
|
||||
/// VFS only
|
||||
static const int SQLITE_OPEN_EXCLUSIVE = 0x00000010;
|
||||
|
||||
/// VFS only
|
||||
static const int SQLITE_OPEN_AUTOPROXY = 0x00000020;
|
||||
|
||||
/// Ok for sqlite3_open_v2()
|
||||
static const int SQLITE_OPEN_URI = 0x00000040;
|
||||
|
||||
/// Ok for sqlite3_open_v2()
|
||||
static const int SQLITE_OPEN_MEMORY = 0x00000080;
|
||||
|
||||
/// VFS only
|
||||
static const int SQLITE_OPEN_MAIN_DB = 0x00000100;
|
||||
|
||||
/// VFS only
|
||||
static const int SQLITE_OPEN_TEMP_DB = 0x00000200;
|
||||
|
||||
/// VFS only
|
||||
static const int SQLITE_OPEN_TRANSIENT_DB = 0x00000400;
|
||||
|
||||
/// VFS only
|
||||
static const int SQLITE_OPEN_MAIN_JOURNAL = 0x00000800;
|
||||
|
||||
/// VFS only
|
||||
static const int SQLITE_OPEN_TEMP_JOURNAL = 0x00001000;
|
||||
|
||||
/// VFS only
|
||||
static const int SQLITE_OPEN_SUBJOURNAL = 0x00002000;
|
||||
|
||||
/// VFS only
|
||||
static const int SQLITE_OPEN_MASTER_JOURNAL = 0x00004000;
|
||||
|
||||
/// Ok for sqlite3_open_v2()
|
||||
static const int SQLITE_OPEN_NOMUTEX = 0x00008000;
|
||||
|
||||
/// Ok for sqlite3_open_v2()
|
||||
static const int SQLITE_OPEN_FULLMUTEX = 0x00010000;
|
||||
|
||||
/// Ok for sqlite3_open_v2()
|
||||
static const int SQLITE_OPEN_SHAREDCACHE = 0x00020000;
|
||||
|
||||
/// Ok for sqlite3_open_v2()
|
||||
static const int SQLITE_OPEN_PRIVATECACHE = 0x00040000;
|
||||
|
||||
/// VFS only
|
||||
static const int SQLITE_OPEN_WAL = 0x00080000;
|
||||
}
|
||||
|
||||
class Types {
|
||||
static const int SQLITE_INTEGER = 1;
|
||||
static const int SQLITE_FLOAT = 2;
|
||||
static const int SQLITE_TEXT = 3;
|
||||
static const int SQLITE_BLOB = 4;
|
||||
static const int SQLITE_NULL = 5;
|
||||
}
|
51
samples/ffi/sqlite/lib/src/bindings/signatures.dart
Normal file
51
samples/ffi/sqlite/lib/src/bindings/signatures.dart
Normal file
|
@ -0,0 +1,51 @@
|
|||
import "dart:ffi";
|
||||
|
||||
import "../ffi/cstring.dart";
|
||||
|
||||
import "types.dart";
|
||||
|
||||
typedef sqlite3_open_v2_native_t = Int32 Function(
|
||||
CString filename, Pointer<DatabasePointer> ppDb, Int32 flags, CString vfs);
|
||||
|
||||
typedef sqlite3_close_v2_native_t = Int32 Function(DatabasePointer database);
|
||||
|
||||
typedef sqlite3_prepare_v2_native_t = Int32 Function(
|
||||
DatabasePointer database,
|
||||
CString query,
|
||||
Int32 nbytes,
|
||||
Pointer<StatementPointer> statementOut,
|
||||
Pointer<CString> tail);
|
||||
|
||||
typedef sqlite3_step_native_t = Int32 Function(StatementPointer statement);
|
||||
|
||||
typedef sqlite3_reset_native_t = Int32 Function(StatementPointer statement);
|
||||
|
||||
typedef sqlite3_finalize_native_t = Int32 Function(StatementPointer statement);
|
||||
|
||||
typedef sqlite3_errstr_native_t = CString Function(Int32 error);
|
||||
|
||||
typedef sqlite3_errmsg_native_t = CString Function(DatabasePointer database);
|
||||
|
||||
typedef sqlite3_column_count_native_t = Int32 Function(
|
||||
StatementPointer statement);
|
||||
|
||||
typedef sqlite3_column_name_native_t = CString Function(
|
||||
StatementPointer statement, Int32 columnIndex);
|
||||
|
||||
typedef sqlite3_column_decltype_native_t = CString Function(
|
||||
StatementPointer statement, Int32 columnIndex);
|
||||
|
||||
typedef sqlite3_column_type_native_t = Int32 Function(
|
||||
StatementPointer statement, Int32 columnIndex);
|
||||
|
||||
typedef sqlite3_column_value_native_t = ValuePointer Function(
|
||||
StatementPointer statement, Int32 columnIndex);
|
||||
|
||||
typedef sqlite3_column_double_native_t = Double Function(
|
||||
StatementPointer statement, Int32 columnIndex);
|
||||
|
||||
typedef sqlite3_column_int_native_t = Int32 Function(
|
||||
StatementPointer statement, Int32 columnIndex);
|
||||
|
||||
typedef sqlite3_column_text_native_t = CString Function(
|
||||
StatementPointer statement, Int32 columnIndex);
|
73
samples/ffi/sqlite/lib/src/bindings/types.dart
Normal file
73
samples/ffi/sqlite/lib/src/bindings/types.dart
Normal file
|
@ -0,0 +1,73 @@
|
|||
import "dart:ffi";
|
||||
|
||||
import "../ffi/cstring.dart";
|
||||
|
||||
/// Database Connection Handle
|
||||
///
|
||||
/// Each open SQLite database is represented by a pointer to an instance of
|
||||
/// the opaque structure named "sqlite3". It is useful to think of an sqlite3
|
||||
/// pointer as an object. The [sqlite3_open()], [sqlite3_open16()], and
|
||||
/// [sqlite3_open_v2()] interfaces are its constructors, and [sqlite3_close()]
|
||||
/// is its destructor. There are many other interfaces (such as
|
||||
/// [sqlite3_prepare_v2()], [sqlite3_create_function()], and
|
||||
/// [sqlite3_busy_timeout()] to name but three) that are methods on an
|
||||
class DatabasePointer extends Pointer<Void> {}
|
||||
|
||||
/// SQL Statement Object
|
||||
///
|
||||
/// An instance of this object represents a single SQL statement.
|
||||
/// This object is variously known as a "prepared statement" or a
|
||||
/// "compiled SQL statement" or simply as a "statement".
|
||||
///
|
||||
/// The life of a statement object goes something like this:
|
||||
///
|
||||
/// <ol>
|
||||
/// <li> Create the object using [sqlite3_prepare_v2()] or a related
|
||||
/// function.
|
||||
/// <li> Bind values to [host parameters] using the sqlite3_bind_*()
|
||||
/// interfaces.
|
||||
/// <li> Run the SQL by calling [sqlite3_step()] one or more times.
|
||||
/// <li> Reset the statement using [sqlite3_reset()] then go back
|
||||
/// to step 2. Do this zero or more times.
|
||||
/// <li> Destroy the object using [sqlite3_finalize()].
|
||||
/// </ol>
|
||||
///
|
||||
/// Refer to documentation on individual methods above for additional
|
||||
/// information.
|
||||
class StatementPointer extends Pointer<Void> {}
|
||||
|
||||
/// Dynamically Typed Value Object
|
||||
///
|
||||
/// SQLite uses the sqlite3_value object to represent all values
|
||||
/// that can be stored in a database table. SQLite uses dynamic typing
|
||||
/// for the values it stores. ^Values stored in sqlite3_value objects
|
||||
/// can be integers, floating point values, strings, BLOBs, or NULL.
|
||||
///
|
||||
/// An sqlite3_value object may be either "protected" or "unprotected".
|
||||
/// Some interfaces require a protected sqlite3_value. Other interfaces
|
||||
/// will accept either a protected or an unprotected sqlite3_value.
|
||||
/// Every interface that accepts sqlite3_value arguments specifies
|
||||
/// whether or not it requires a protected sqlite3_value.
|
||||
///
|
||||
/// The terms "protected" and "unprotected" refer to whether or not
|
||||
/// a mutex is held. An internal mutex is held for a protected
|
||||
/// sqlite3_value object but no mutex is held for an unprotected
|
||||
/// sqlite3_value object. If SQLite is compiled to be single-threaded
|
||||
/// (with [SQLITE_THREADSAFE=0] and with [sqlite3_threadsafe()] returning 0)
|
||||
/// or if SQLite is run in one of reduced mutex modes
|
||||
/// [SQLITE_CONFIG_SINGLETHREAD] or [SQLITE_CONFIG_MULTITHREAD]
|
||||
/// then there is no distinction between protected and unprotected
|
||||
/// sqlite3_value objects and they can be used interchangeably. However,
|
||||
/// for maximum code portability it is recommended that applications
|
||||
/// still make the distinction between protected and unprotected
|
||||
/// sqlite3_value objects even when not strictly required.
|
||||
///
|
||||
/// ^The sqlite3_value objects that are passed as parameters into the
|
||||
/// implementation of [application-defined SQL functions] are protected.
|
||||
/// ^The sqlite3_value object returned by
|
||||
/// [sqlite3_column_value()] is unprotected.
|
||||
/// Unprotected sqlite3_value objects may only be used with
|
||||
/// [sqlite3_result_value()] and [sqlite3_bind_value()].
|
||||
/// The [sqlite3_value_blob | sqlite3_value_type()] family of
|
||||
/// interfaces require protected sqlite3_value objects.
|
||||
class ValuePointer extends Pointer<Void> {}
|
|
@ -0,0 +1,25 @@
|
|||
/// This iterator should be [close]d after use.
|
||||
///
|
||||
/// [ClosableIterator]s often use resources which should be freed after use.
|
||||
/// The consumer of the iterator can either manually [close] the iterator, or
|
||||
/// consume all elements on which the iterator will automatically be closed.
|
||||
abstract class ClosableIterator<T> extends Iterator<T> {
|
||||
/// Close this iterator.
|
||||
void close();
|
||||
|
||||
/// Moves to the next element and [close]s the iterator if it was the last
|
||||
/// element.
|
||||
bool moveNext();
|
||||
}
|
||||
|
||||
/// This iterable's iterator should be [close]d after use.
|
||||
///
|
||||
/// Companion class of [ClosableIterator].
|
||||
abstract class ClosableIterable<T> extends Iterable<T> {
|
||||
/// Close this iterables iterator.
|
||||
void close();
|
||||
|
||||
/// Returns a [ClosableIterator] that allows iterating the elements of this
|
||||
/// [ClosableIterable].
|
||||
ClosableIterator<T> get iterator;
|
||||
}
|
303
samples/ffi/sqlite/lib/src/database.dart
Normal file
303
samples/ffi/sqlite/lib/src/database.dart
Normal file
|
@ -0,0 +1,303 @@
|
|||
import "dart:collection";
|
||||
import "dart:ffi";
|
||||
|
||||
import "bindings/bindings.dart";
|
||||
import "bindings/types.dart";
|
||||
import "bindings/constants.dart";
|
||||
import "collections/closable_iterator.dart";
|
||||
import "ffi/cstring.dart";
|
||||
|
||||
/// [Database] represents an open connection to a SQLite database.
|
||||
///
|
||||
/// All functions against a database may throw [SQLiteError].
|
||||
///
|
||||
/// This database interacts with SQLite synchonously.
|
||||
class Database {
|
||||
DatabasePointer _database;
|
||||
bool _open = false;
|
||||
|
||||
/// Open a database located at the file [path].
|
||||
Database(String path,
|
||||
[int flags = Flags.SQLITE_OPEN_READWRITE | Flags.SQLITE_OPEN_CREATE]) {
|
||||
Pointer<DatabasePointer> dbOut = allocate();
|
||||
CString pathC = CString.allocate(path);
|
||||
final int resultCode =
|
||||
bindings.sqlite3_open_v2(pathC, dbOut, flags, fromAddress(0));
|
||||
_database = dbOut.load();
|
||||
dbOut.free();
|
||||
pathC.free();
|
||||
|
||||
if (resultCode == Errors.SQLITE_OK) {
|
||||
_open = true;
|
||||
} else {
|
||||
// Even if "open" fails, sqlite3 will still create a database object. We
|
||||
// can just destroy it.
|
||||
SQLiteException exception = _loadError(resultCode);
|
||||
close();
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
/// Close the database.
|
||||
///
|
||||
/// This should only be called once on a database unless an exception is
|
||||
/// thrown. It should be called at least once to finalize the database and
|
||||
/// avoid resource leaks.
|
||||
void close() {
|
||||
assert(_open);
|
||||
final int resultCode = bindings.sqlite3_close_v2(_database);
|
||||
if (resultCode == Errors.SQLITE_OK) {
|
||||
_open = false;
|
||||
} else {
|
||||
throw _loadError(resultCode);
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute a query, discarding any returned rows.
|
||||
void execute(String query) {
|
||||
Pointer<StatementPointer> statementOut = allocate();
|
||||
CString queryC = CString.allocate(query);
|
||||
int resultCode = bindings.sqlite3_prepare_v2(
|
||||
_database, queryC, -1, statementOut, fromAddress(0));
|
||||
StatementPointer statement = statementOut.load();
|
||||
statementOut.free();
|
||||
queryC.free();
|
||||
|
||||
while (resultCode == Errors.SQLITE_ROW || resultCode == Errors.SQLITE_OK) {
|
||||
resultCode = bindings.sqlite3_step(statement);
|
||||
}
|
||||
bindings.sqlite3_finalize(statement);
|
||||
if (resultCode != Errors.SQLITE_DONE) {
|
||||
throw _loadError(resultCode);
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate a query and return the resulting rows as an iterable.
|
||||
Result query(String query) {
|
||||
Pointer<StatementPointer> statementOut = allocate();
|
||||
CString queryC = CString.allocate(query);
|
||||
int resultCode = bindings.sqlite3_prepare_v2(
|
||||
_database, queryC, -1, statementOut, fromAddress(0));
|
||||
StatementPointer statement = statementOut.load();
|
||||
statementOut.free();
|
||||
queryC.free();
|
||||
|
||||
if (resultCode != Errors.SQLITE_OK) {
|
||||
bindings.sqlite3_finalize(statement);
|
||||
throw _loadError(resultCode);
|
||||
}
|
||||
|
||||
Map<String, int> columnIndices = {};
|
||||
int columnCount = bindings.sqlite3_column_count(statement);
|
||||
for (int i = 0; i < columnCount; i++) {
|
||||
String columnName =
|
||||
CString.fromUtf8(bindings.sqlite3_column_name(statement, i));
|
||||
columnIndices[columnName] = i;
|
||||
}
|
||||
|
||||
return Result._(this, statement, columnIndices);
|
||||
}
|
||||
|
||||
SQLiteException _loadError([int errorCode]) {
|
||||
String errorMessage = CString.fromUtf8(bindings.sqlite3_errmsg(_database));
|
||||
if (errorCode == null) {
|
||||
return SQLiteException(errorMessage);
|
||||
}
|
||||
String errorCodeExplanation =
|
||||
CString.fromUtf8(bindings.sqlite3_errstr(errorCode));
|
||||
return SQLiteException(
|
||||
"$errorMessage (Code $errorCode: $errorCodeExplanation)");
|
||||
}
|
||||
}
|
||||
|
||||
/// [Result] represents a [Database.query]'s result and provides an [Iterable]
|
||||
/// interface for the results to be consumed.
|
||||
///
|
||||
/// Please note that this iterator should be [close]d manually if not all [Row]s
|
||||
/// are consumed.
|
||||
class Result extends IterableBase<Row> implements ClosableIterable<Row> {
|
||||
final Database _database;
|
||||
final ClosableIterator<Row> _iterator;
|
||||
final StatementPointer _statement;
|
||||
final Map<String, int> _columnIndices;
|
||||
|
||||
Row _currentRow = null;
|
||||
|
||||
Result._(
|
||||
this._database,
|
||||
this._statement,
|
||||
this._columnIndices,
|
||||
) : _iterator = _ResultIterator(_statement, _columnIndices) {}
|
||||
|
||||
void close() => _iterator.close();
|
||||
|
||||
ClosableIterator<Row> get iterator => _iterator;
|
||||
}
|
||||
|
||||
class _ResultIterator implements ClosableIterator<Row> {
|
||||
final StatementPointer _statement;
|
||||
final Map<String, int> _columnIndices;
|
||||
|
||||
Row _currentRow = null;
|
||||
bool _closed = false;
|
||||
|
||||
_ResultIterator(this._statement, this._columnIndices) {}
|
||||
|
||||
bool moveNext() {
|
||||
if (_closed) {
|
||||
throw SQLiteException("The result has already been closed.");
|
||||
}
|
||||
_currentRow?._setNotCurrent();
|
||||
int stepResult = bindings.sqlite3_step(_statement);
|
||||
if (stepResult == Errors.SQLITE_ROW) {
|
||||
_currentRow = Row._(_statement, _columnIndices);
|
||||
return true;
|
||||
} else {
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Row get current {
|
||||
if (_closed) {
|
||||
throw SQLiteException("The result has already been closed.");
|
||||
}
|
||||
return _currentRow;
|
||||
}
|
||||
|
||||
void close() {
|
||||
_currentRow?._setNotCurrent();
|
||||
_closed = true;
|
||||
bindings.sqlite3_finalize(_statement);
|
||||
}
|
||||
}
|
||||
|
||||
class Row {
|
||||
final StatementPointer _statement;
|
||||
final Map<String, int> _columnIndices;
|
||||
|
||||
bool _isCurrentRow = true;
|
||||
|
||||
Row._(this._statement, this._columnIndices) {}
|
||||
|
||||
/// Reads column [columnName].
|
||||
///
|
||||
/// By default it returns a dynamically typed value. If [convert] is set to
|
||||
/// [Convert.StaticType] the value is converted to the static type computed
|
||||
/// for the column by the query compiler.
|
||||
dynamic readColumn(String columnName,
|
||||
{Convert convert = Convert.DynamicType}) {
|
||||
return readColumnByIndex(_columnIndices[columnName], convert: convert);
|
||||
}
|
||||
|
||||
/// Reads column [columnName].
|
||||
///
|
||||
/// By default it returns a dynamically typed value. If [convert] is set to
|
||||
/// [Convert.StaticType] the value is converted to the static type computed
|
||||
/// for the column by the query compiler.
|
||||
dynamic readColumnByIndex(int columnIndex,
|
||||
{Convert convert = Convert.DynamicType}) {
|
||||
_checkIsCurrentRow();
|
||||
|
||||
Type dynamicType;
|
||||
if (convert == Convert.DynamicType) {
|
||||
dynamicType =
|
||||
_typeFromCode(bindings.sqlite3_column_type(_statement, columnIndex));
|
||||
} else {
|
||||
dynamicType = _typeFromText(CString.fromUtf8(
|
||||
bindings.sqlite3_column_decltype(_statement, columnIndex)));
|
||||
}
|
||||
|
||||
switch (dynamicType) {
|
||||
case Type.Integer:
|
||||
return readColumnByIndexAsInt(columnIndex);
|
||||
case Type.Text:
|
||||
return readColumnByIndexAsText(columnIndex);
|
||||
case Type.Null:
|
||||
return null;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads column [columnName] and converts to [Type.Integer] if not an
|
||||
/// integer.
|
||||
int readColumnAsInt(String columnName) {
|
||||
return readColumnByIndexAsInt(_columnIndices[columnName]);
|
||||
}
|
||||
|
||||
/// Reads column [columnIndex] and converts to [Type.Integer] if not an
|
||||
/// integer.
|
||||
int readColumnByIndexAsInt(int columnIndex) {
|
||||
_checkIsCurrentRow();
|
||||
return bindings.sqlite3_column_int(_statement, columnIndex);
|
||||
}
|
||||
|
||||
/// Reads column [columnName] and converts to [Type.Text] if not text.
|
||||
String readColumnAsText(String columnName) {
|
||||
return readColumnByIndexAsText(_columnIndices[columnName]);
|
||||
}
|
||||
|
||||
/// Reads column [columnIndex] and converts to [Type.Text] if not text.
|
||||
String readColumnByIndexAsText(int columnIndex) {
|
||||
_checkIsCurrentRow();
|
||||
return CString.fromUtf8(
|
||||
bindings.sqlite3_column_text(_statement, columnIndex));
|
||||
}
|
||||
|
||||
void _checkIsCurrentRow() {
|
||||
if (!_isCurrentRow) {
|
||||
throw Exception(
|
||||
"This row is not the current row, reading data from the non-current"
|
||||
" row is not supported by sqlite.");
|
||||
}
|
||||
}
|
||||
|
||||
void _setNotCurrent() {
|
||||
_isCurrentRow = false;
|
||||
}
|
||||
}
|
||||
|
||||
Type _typeFromCode(int code) {
|
||||
switch (code) {
|
||||
case Types.SQLITE_INTEGER:
|
||||
return Type.Integer;
|
||||
case Types.SQLITE_FLOAT:
|
||||
return Type.Float;
|
||||
case Types.SQLITE_TEXT:
|
||||
return Type.Text;
|
||||
case Types.SQLITE_BLOB:
|
||||
return Type.Blob;
|
||||
case Types.SQLITE_NULL:
|
||||
return Type.Null;
|
||||
}
|
||||
throw Exception("Unknown type [$code]");
|
||||
}
|
||||
|
||||
Type _typeFromText(String textRepresentation) {
|
||||
switch (textRepresentation) {
|
||||
case "integer":
|
||||
return Type.Integer;
|
||||
case "float":
|
||||
return Type.Float;
|
||||
case "text":
|
||||
return Type.Text;
|
||||
case "blob":
|
||||
return Type.Blob;
|
||||
case "null":
|
||||
return Type.Null;
|
||||
}
|
||||
if (textRepresentation == null) return Type.Null;
|
||||
throw Exception("Unknown type [$textRepresentation]");
|
||||
}
|
||||
|
||||
enum Type { Integer, Float, Text, Blob, Null }
|
||||
|
||||
enum Convert { DynamicType, StaticType }
|
||||
|
||||
class SQLiteException {
|
||||
final String message;
|
||||
SQLiteException(this.message);
|
||||
|
||||
String toString() => message;
|
||||
}
|
53
samples/ffi/sqlite/lib/src/ffi/arena.dart
Normal file
53
samples/ffi/sqlite/lib/src/ffi/arena.dart
Normal file
|
@ -0,0 +1,53 @@
|
|||
import "dart:async";
|
||||
import "dart:ffi";
|
||||
|
||||
/// [Arena] manages allocated C memory.
|
||||
///
|
||||
/// Arenas are zoned.
|
||||
class Arena {
|
||||
Arena();
|
||||
|
||||
List<Pointer<Void>> _allocations = [];
|
||||
|
||||
/// Bound the lifetime of [ptr] to this [Arena].
|
||||
T scoped<T extends Pointer>(T ptr) {
|
||||
_allocations.add(ptr.cast());
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/// Frees all memory pointed to by [Pointer]s in this arena.
|
||||
void finalize() {
|
||||
for (final ptr in _allocations) {
|
||||
ptr.free();
|
||||
}
|
||||
}
|
||||
|
||||
/// The last [Arena] in the zone.
|
||||
factory Arena.current() {
|
||||
return Zone.current[#_currentArena];
|
||||
}
|
||||
}
|
||||
|
||||
/// Bound the lifetime of [ptr] to the current [Arena].
|
||||
T scoped<T extends Pointer>(T ptr) => Arena.current().scoped(ptr);
|
||||
|
||||
class RethrownError {
|
||||
dynamic original;
|
||||
StackTrace originalStackTrace;
|
||||
RethrownError(this.original, this.originalStackTrace);
|
||||
toString() => """RethrownError(${original})
|
||||
${originalStackTrace}""";
|
||||
}
|
||||
|
||||
/// Runs the [body] in an [Arena] freeing all memory which is [scoped] during
|
||||
/// execution of [body] at the end of the execution.
|
||||
R runArena<R>(R Function(Arena) body) {
|
||||
Arena arena = Arena();
|
||||
try {
|
||||
return runZoned(() => body(arena),
|
||||
zoneValues: {#_currentArena: arena},
|
||||
onError: (error, st) => throw RethrownError(error, st));
|
||||
} finally {
|
||||
arena.finalize();
|
||||
}
|
||||
}
|
39
samples/ffi/sqlite/lib/src/ffi/cstring.dart
Normal file
39
samples/ffi/sqlite/lib/src/ffi/cstring.dart
Normal file
|
@ -0,0 +1,39 @@
|
|||
import "dart:convert";
|
||||
import "dart:ffi";
|
||||
|
||||
import "arena.dart";
|
||||
|
||||
/// Represents a String in C memory, managed by an [Arena].
|
||||
class CString extends Pointer<Int8> {
|
||||
/// Allocates a [CString] in the current [Arena] and populates it with
|
||||
/// [dartStr].
|
||||
factory CString(String dartStr) => CString.inArena(Arena.current(), dartStr);
|
||||
|
||||
/// Allocates a [CString] in [arena] and populates it with [dartStr].
|
||||
factory CString.inArena(Arena arena, String dartStr) =>
|
||||
arena.scoped(CString.allocate(dartStr));
|
||||
|
||||
/// Allocate a [CString] not managed in and populates it with [dartStr].
|
||||
///
|
||||
/// This [CString] is not managed by an [Arena]. Please ensure to [free] the
|
||||
/// memory manually!
|
||||
factory CString.allocate(String dartStr) {
|
||||
List<int> units = Utf8Encoder().convert(dartStr);
|
||||
Pointer<Int8> str = allocate(count: units.length + 1);
|
||||
for (int i = 0; i < units.length; ++i) {
|
||||
str.elementAt(i).store(units[i]);
|
||||
}
|
||||
str.elementAt(units.length).store(0);
|
||||
return str.cast();
|
||||
}
|
||||
|
||||
/// Read the string for C memory into Dart.
|
||||
static String fromUtf8(CString str) {
|
||||
if (str == null) return null;
|
||||
int len = 0;
|
||||
while (str.elementAt(++len).load<int>() != 0);
|
||||
List<int> units = List(len);
|
||||
for (int i = 0; i < len; ++i) units[i] = str.elementAt(i).load();
|
||||
return Utf8Decoder().convert(units);
|
||||
}
|
||||
}
|
19
samples/ffi/sqlite/lib/src/ffi/dylib_utils.dart
Normal file
19
samples/ffi/sqlite/lib/src/ffi/dylib_utils.dart
Normal file
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'dart:ffi' as ffi;
|
||||
import 'dart:io' show Platform;
|
||||
|
||||
String _platformPath(String name, {String path}) {
|
||||
if (path == null) path = "";
|
||||
if (Platform.isLinux) return path + "lib" + name + ".so";
|
||||
if (Platform.isMacOS) return path + "lib" + name + ".dylib";
|
||||
if (Platform.isWindows) return path + name + ".dll";
|
||||
throw Exception("Platform not implemented");
|
||||
}
|
||||
|
||||
ffi.DynamicLibrary dlopenPlatformSpecific(String name, {String path}) {
|
||||
String fullPath = _platformPath(name, path: path);
|
||||
return ffi.DynamicLibrary.open(fullPath);
|
||||
}
|
9
samples/ffi/sqlite/pubspec.yaml
Normal file
9
samples/ffi/sqlite/pubspec.yaml
Normal file
|
@ -0,0 +1,9 @@
|
|||
name: sqlite3
|
||||
version: 0.0.1
|
||||
description: >-
|
||||
Sqlite3 wrapper. Demo for dart:ffi.
|
||||
author: Daco Harkes <dacoharkes@google.com>, Samir Jindel <sjindel@google.com>
|
||||
environment:
|
||||
sdk: '>=2.1.0 <3.0.0'
|
||||
dev_dependencies:
|
||||
test: ^1.5.3
|
160
samples/ffi/sqlite/test/sqlite_test.dart
Normal file
160
samples/ffi/sqlite/test/sqlite_test.dart
Normal file
|
@ -0,0 +1,160 @@
|
|||
// VMOptions=--optimization-counter-threshold=5
|
||||
|
||||
import "package:test/test.dart";
|
||||
|
||||
import 'package:sqlite3/sqlite.dart';
|
||||
|
||||
void main() {
|
||||
test("sqlite integration test", () {
|
||||
Database d = Database("test.db");
|
||||
d.execute("drop table if exists Cookies;");
|
||||
d.execute("""
|
||||
create table Cookies (
|
||||
id integer primary key,
|
||||
name text not null,
|
||||
alternative_name text
|
||||
);""");
|
||||
d.execute("""
|
||||
insert into Cookies (id, name, alternative_name)
|
||||
values
|
||||
(1,'Chocolade chip cookie', 'Chocolade cookie'),
|
||||
(2,'Ginger cookie', null),
|
||||
(3,'Cinnamon roll', null)
|
||||
;""");
|
||||
Result result = d.query("""
|
||||
select
|
||||
id,
|
||||
name,
|
||||
alternative_name,
|
||||
case
|
||||
when id=1 then 'foo'
|
||||
when id=2 then 42
|
||||
when id=3 then null
|
||||
end as multi_typed_column
|
||||
from Cookies
|
||||
;""");
|
||||
for (Row r in result) {
|
||||
int id = r.readColumnAsInt("id");
|
||||
expect(true, 1 <= id && id <= 3);
|
||||
String name = r.readColumnByIndex(1);
|
||||
expect(true, name is String);
|
||||
String alternativeName = r.readColumn("alternative_name");
|
||||
expect(true, alternativeName is String || alternativeName == null);
|
||||
dynamic multiTypedValue = r.readColumn("multi_typed_column");
|
||||
expect(
|
||||
true,
|
||||
multiTypedValue == 42 ||
|
||||
multiTypedValue == 'foo' ||
|
||||
multiTypedValue == null);
|
||||
print("$id $name $alternativeName $multiTypedValue");
|
||||
}
|
||||
result = d.query("""
|
||||
select
|
||||
id,
|
||||
name,
|
||||
alternative_name,
|
||||
case
|
||||
when id=1 then 'foo'
|
||||
when id=2 then 42
|
||||
when id=3 then null
|
||||
end as multi_typed_column
|
||||
from Cookies
|
||||
;""");
|
||||
for (Row r in result) {
|
||||
int id = r.readColumnAsInt("id");
|
||||
expect(true, 1 <= id && id <= 3);
|
||||
String name = r.readColumnByIndex(1);
|
||||
expect(true, name is String);
|
||||
String alternativeName = r.readColumn("alternative_name");
|
||||
expect(true, alternativeName is String || alternativeName == null);
|
||||
dynamic multiTypedValue = r.readColumn("multi_typed_column");
|
||||
expect(
|
||||
true,
|
||||
multiTypedValue == 42 ||
|
||||
multiTypedValue == 'foo' ||
|
||||
multiTypedValue == null);
|
||||
print("$id $name $alternativeName $multiTypedValue");
|
||||
if (id == 2) {
|
||||
result.close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
try {
|
||||
result.iterator.moveNext();
|
||||
} on SQLiteException catch (e) {
|
||||
print("expected exception on accessing result data after close: $e");
|
||||
}
|
||||
try {
|
||||
d.query("""
|
||||
select
|
||||
id,
|
||||
non_existing_column
|
||||
from Cookies
|
||||
;""");
|
||||
} on SQLiteException catch (e) {
|
||||
print("expected this query to fail: $e");
|
||||
}
|
||||
d.execute("drop table Cookies;");
|
||||
d.close();
|
||||
});
|
||||
|
||||
test("concurrent db open and queries", () {
|
||||
Database d = Database("test.db");
|
||||
Database d2 = Database("test.db");
|
||||
d.execute("drop table if exists Cookies;");
|
||||
d.execute("""
|
||||
create table Cookies (
|
||||
id integer primary key,
|
||||
name text not null,
|
||||
alternative_name text
|
||||
);""");
|
||||
d.execute("""
|
||||
insert into Cookies (id, name, alternative_name)
|
||||
values
|
||||
(1,'Chocolade chip cookie', 'Chocolade cookie'),
|
||||
(2,'Ginger cookie', null),
|
||||
(3,'Cinnamon roll', null)
|
||||
;""");
|
||||
Result r = d.query("select * from Cookies;");
|
||||
Result r2 = d2.query("select * from Cookies;");
|
||||
r.iterator..moveNext();
|
||||
r2.iterator..moveNext();
|
||||
r.iterator..moveNext();
|
||||
Result r3 = d2.query("select * from Cookies;");
|
||||
r3.iterator..moveNext();
|
||||
expect(2, r.iterator.current.readColumn("id"));
|
||||
expect(1, r2.iterator.current.readColumn("id"));
|
||||
expect(1, r3.iterator.current.readColumn("id"));
|
||||
r.close();
|
||||
r2.close();
|
||||
r3.close();
|
||||
d.close();
|
||||
d2.close();
|
||||
});
|
||||
|
||||
test("stress test", () {
|
||||
Database d = Database("test.db");
|
||||
d.execute("drop table if exists Cookies;");
|
||||
d.execute("""
|
||||
create table Cookies (
|
||||
id integer primary key,
|
||||
name text not null,
|
||||
alternative_name text
|
||||
);""");
|
||||
int repeats = 100;
|
||||
for (int i = 0; i < repeats; i++) {
|
||||
d.execute("""
|
||||
insert into Cookies (name, alternative_name)
|
||||
values
|
||||
('Chocolade chip cookie', 'Chocolade cookie'),
|
||||
('Ginger cookie', null),
|
||||
('Cinnamon roll', null)
|
||||
;""");
|
||||
}
|
||||
Result r = d.query("select count(*) from Cookies;");
|
||||
int count = r.first.readColumnByIndexAsInt(0);
|
||||
expect(count, 3 * repeats);
|
||||
r.close();
|
||||
d.close();
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue