├── .github
└── workflows
│ ├── ci.yml
│ └── release.yml
├── .gitignore
├── LICENSE
├── README.md
├── UnitTests
├── Properties
│ └── AssemblyInfo.cs
├── UnitTestDuckDbQueries.cs
├── UnitTests.csproj
├── app.config
└── packages.config
├── images
├── duckdb_http_parquet.gif
├── logo.png
├── logo_rect.png
└── xlduckdb_unblock.gif
├── xlDuckDb.sln
└── xlDuckDb
├── ArrayHelper.cs
├── DuckDbHelper.cs
├── Properties
├── AssemblyInfo.cs
└── ExcelDna.Build.props
├── app.config
├── packages.config
├── xlAddIn.cs
├── xlDuckDb.csproj
└── xlDuckDb.dna
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | push:
7 | branches: [ "main" ]
8 | pull_request:
9 | branches: [ "main" ]
10 |
11 | jobs:
12 |
13 | build:
14 | runs-on: windows-2019
15 |
16 | steps:
17 |
18 | - name: Checkout
19 | uses: actions/checkout@v4
20 | with:
21 | fetch-depth: 0
22 |
23 | # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild
24 | - name: Setup MSBuild.exe
25 | uses: microsoft/setup-msbuild@v1
26 |
27 | # Setup vstest
28 | - name: Setup VSTest
29 | uses: darenm/Setup-VSTest@v1
30 |
31 | # Restore nuget packages
32 | - name: Restore packages
33 | run: nuget restore xlDuckDb.sln
34 |
35 | # Build
36 | - name: Build solution
37 | run: msbuild xlDuckDb.sln -t:rebuild -property:Configuration=Release -property:ExcelDnaCreate32BitAddIn=false -property:ExcelDna64BitAddInSuffix= -property:RunExcelDnaPack=false -property:ExcelDna32BitAddInSuffix=32
38 |
39 | # Run tests
40 | - name: Unit test
41 | run: vstest.console.exe /Platform:x64 .\UnitTests\bin\x64\Release\UnitTests.dll
42 |
43 | # Upload binary
44 | - name: "Upload binary"
45 | uses: actions/upload-artifact@v4
46 | with:
47 | name: xlduckdb-${{ github.sha }}
48 | path: .\xlDuckDb\bin\x64\Release\*.*
49 | retention-days: 1
50 |
51 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Releases
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*'
7 |
8 | jobs:
9 | build:
10 | runs-on: windows-2019
11 | permissions:
12 | contents: write
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v4
16 | with:
17 | fetch-depth: 0
18 |
19 | # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild
20 | - name: Setup MSBuild.exe
21 | uses: microsoft/setup-msbuild@v1
22 |
23 | # Setup vstest
24 | - name: Setup VSTest
25 | uses: darenm/Setup-VSTest@v1
26 |
27 | # Restore nuget packages
28 | - name: Restore packages
29 | run: nuget restore xlDuckDb.sln
30 |
31 | # Build
32 | - name: Build solution
33 | run: msbuild xlDuckDb.sln -t:rebuild -property:Configuration=Release -property:ExcelDnaCreate32BitAddIn=false -property:ExcelDna64BitAddInSuffix= -property:RunExcelDnaPack=false -property:ExcelDna32BitAddInSuffix=32
34 |
35 | # Run tests
36 | - name: Unit test
37 | run: vstest.console.exe /Platform:x64 .\UnitTests\bin\x64\Release\UnitTests.dll
38 |
39 | # Upload binary
40 | - name: "Upload binary"
41 | uses: actions/upload-artifact@v4
42 | with:
43 | name: xlduckdb-${{ github.sha }}
44 | path: .\xlDuckDb\bin\x64\Release\*.*
45 | retention-days: 1
46 |
47 | # Never mind that the previous step just did this...
48 | - name: ZIP release artifact
49 | run: Compress-Archive -Path ./xlDuckDb/bin/x64/Release/*.* -Destination xlduckdb.zip
50 |
51 | # Create release
52 | - uses: ncipollo/release-action@v1
53 | with:
54 | artifacts: "xlduckdb.zip"
55 | makeLatest: true
56 | allowUpdates: true
57 | artifactErrorsFailBuild: true
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Ll]og/
33 | [Ll]ogs/
34 |
35 | # Visual Studio 2015/2017 cache/options directory
36 | .vs/
37 | # Uncomment if you have tasks that create the project's static files in wwwroot
38 | #wwwroot/
39 |
40 | # Visual Studio 2017 auto generated files
41 | Generated\ Files/
42 |
43 | # MSTest test Results
44 | [Tt]est[Rr]esult*/
45 | [Bb]uild[Ll]og.*
46 |
47 | # NUnit
48 | *.VisualState.xml
49 | TestResult.xml
50 | nunit-*.xml
51 |
52 | # Build Results of an ATL Project
53 | [Dd]ebugPS/
54 | [Rr]eleasePS/
55 | dlldata.c
56 |
57 | # Benchmark Results
58 | BenchmarkDotNet.Artifacts/
59 |
60 | # .NET Core
61 | project.lock.json
62 | project.fragment.lock.json
63 | artifacts/
64 |
65 | # ASP.NET Scaffolding
66 | ScaffoldingReadMe.txt
67 |
68 | # StyleCop
69 | StyleCopReport.xml
70 |
71 | # Files built by Visual Studio
72 | *_i.c
73 | *_p.c
74 | *_h.h
75 | *.ilk
76 | *.meta
77 | *.obj
78 | *.iobj
79 | *.pch
80 | *.pdb
81 | *.ipdb
82 | *.pgc
83 | *.pgd
84 | *.rsp
85 | *.sbr
86 | *.tlb
87 | *.tli
88 | *.tlh
89 | *.tmp
90 | *.tmp_proj
91 | *_wpftmp.csproj
92 | *.log
93 | *.tlog
94 | *.vspscc
95 | *.vssscc
96 | .builds
97 | *.pidb
98 | *.svclog
99 | *.scc
100 |
101 | # Chutzpah Test files
102 | _Chutzpah*
103 |
104 | # Visual C++ cache files
105 | ipch/
106 | *.aps
107 | *.ncb
108 | *.opendb
109 | *.opensdf
110 | *.sdf
111 | *.cachefile
112 | *.VC.db
113 | *.VC.VC.opendb
114 |
115 | # Visual Studio profiler
116 | *.psess
117 | *.vsp
118 | *.vspx
119 | *.sap
120 |
121 | # Visual Studio Trace Files
122 | *.e2e
123 |
124 | # TFS 2012 Local Workspace
125 | $tf/
126 |
127 | # Guidance Automation Toolkit
128 | *.gpState
129 |
130 | # ReSharper is a .NET coding add-in
131 | _ReSharper*/
132 | *.[Rr]e[Ss]harper
133 | *.DotSettings.user
134 |
135 | # TeamCity is a build add-in
136 | _TeamCity*
137 |
138 | # DotCover is a Code Coverage Tool
139 | *.dotCover
140 |
141 | # AxoCover is a Code Coverage Tool
142 | .axoCover/*
143 | !.axoCover/settings.json
144 |
145 | # Coverlet is a free, cross platform Code Coverage Tool
146 | coverage*.json
147 | coverage*.xml
148 | coverage*.info
149 |
150 | # Visual Studio code coverage results
151 | *.coverage
152 | *.coveragexml
153 |
154 | # NCrunch
155 | _NCrunch_*
156 | .*crunch*.local.xml
157 | nCrunchTemp_*
158 |
159 | # MightyMoose
160 | *.mm.*
161 | AutoTest.Net/
162 |
163 | # Web workbench (sass)
164 | .sass-cache/
165 |
166 | # Installshield output folder
167 | [Ee]xpress/
168 |
169 | # DocProject is a documentation generator add-in
170 | DocProject/buildhelp/
171 | DocProject/Help/*.HxT
172 | DocProject/Help/*.HxC
173 | DocProject/Help/*.hhc
174 | DocProject/Help/*.hhk
175 | DocProject/Help/*.hhp
176 | DocProject/Help/Html2
177 | DocProject/Help/html
178 |
179 | # Click-Once directory
180 | publish/
181 |
182 | # Publish Web Output
183 | *.[Pp]ublish.xml
184 | *.azurePubxml
185 | # Note: Comment the next line if you want to checkin your web deploy settings,
186 | # but database connection strings (with potential passwords) will be unencrypted
187 | *.pubxml
188 | *.publishproj
189 |
190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
191 | # checkin your Azure Web App publish settings, but sensitive information contained
192 | # in these scripts will be unencrypted
193 | PublishScripts/
194 |
195 | # NuGet Packages
196 | *.nupkg
197 | # NuGet Symbol Packages
198 | *.snupkg
199 | # The packages folder can be ignored because of Package Restore
200 | **/[Pp]ackages/*
201 | # except build/, which is used as an MSBuild target.
202 | !**/[Pp]ackages/build/
203 | # Uncomment if necessary however generally it will be regenerated when needed
204 | #!**/[Pp]ackages/repositories.config
205 | # NuGet v3's project.json files produces more ignorable files
206 | *.nuget.props
207 | *.nuget.targets
208 |
209 | # Microsoft Azure Build Output
210 | csx/
211 | *.build.csdef
212 |
213 | # Microsoft Azure Emulator
214 | ecf/
215 | rcf/
216 |
217 | # Windows Store app package directories and files
218 | AppPackages/
219 | BundleArtifacts/
220 | Package.StoreAssociation.xml
221 | _pkginfo.txt
222 | *.appx
223 | *.appxbundle
224 | *.appxupload
225 |
226 | # Visual Studio cache files
227 | # files ending in .cache can be ignored
228 | *.[Cc]ache
229 | # but keep track of directories ending in .cache
230 | !?*.[Cc]ache/
231 |
232 | # Others
233 | ClientBin/
234 | ~$*
235 | *~
236 | *.dbmdl
237 | *.dbproj.schemaview
238 | *.jfm
239 | *.pfx
240 | *.publishsettings
241 | orleans.codegen.cs
242 |
243 | # Including strong name files can present a security risk
244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
245 | #*.snk
246 |
247 | # Since there are multiple workflows, uncomment next line to ignore bower_components
248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
249 | #bower_components/
250 |
251 | # RIA/Silverlight projects
252 | Generated_Code/
253 |
254 | # Backup & report files from converting an old project file
255 | # to a newer Visual Studio version. Backup files are not needed,
256 | # because we have git ;-)
257 | _UpgradeReport_Files/
258 | Backup*/
259 | UpgradeLog*.XML
260 | UpgradeLog*.htm
261 | ServiceFabricBackup/
262 | *.rptproj.bak
263 |
264 | # SQL Server files
265 | *.mdf
266 | *.ldf
267 | *.ndf
268 |
269 | # Business Intelligence projects
270 | *.rdl.data
271 | *.bim.layout
272 | *.bim_*.settings
273 | *.rptproj.rsuser
274 | *- [Bb]ackup.rdl
275 | *- [Bb]ackup ([0-9]).rdl
276 | *- [Bb]ackup ([0-9][0-9]).rdl
277 |
278 | # Microsoft Fakes
279 | FakesAssemblies/
280 |
281 | # GhostDoc plugin setting file
282 | *.GhostDoc.xml
283 |
284 | # Node.js Tools for Visual Studio
285 | .ntvs_analysis.dat
286 | node_modules/
287 |
288 | # Visual Studio 6 build log
289 | *.plg
290 |
291 | # Visual Studio 6 workspace options file
292 | *.opt
293 |
294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
295 | *.vbw
296 |
297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.)
298 | *.vbp
299 |
300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project)
301 | *.dsw
302 | *.dsp
303 |
304 | # Visual Studio 6 technical files
305 | *.ncb
306 | *.aps
307 |
308 | # Visual Studio LightSwitch build output
309 | **/*.HTMLClient/GeneratedArtifacts
310 | **/*.DesktopClient/GeneratedArtifacts
311 | **/*.DesktopClient/ModelManifest.xml
312 | **/*.Server/GeneratedArtifacts
313 | **/*.Server/ModelManifest.xml
314 | _Pvt_Extensions
315 |
316 | # Paket dependency manager
317 | .paket/paket.exe
318 | paket-files/
319 |
320 | # FAKE - F# Make
321 | .fake/
322 |
323 | # CodeRush personal settings
324 | .cr/personal
325 |
326 | # Python Tools for Visual Studio (PTVS)
327 | __pycache__/
328 | *.pyc
329 |
330 | # Cake - Uncomment if you are using it
331 | # tools/**
332 | # !tools/packages.config
333 |
334 | # Tabs Studio
335 | *.tss
336 |
337 | # Telerik's JustMock configuration file
338 | *.jmconfig
339 |
340 | # BizTalk build output
341 | *.btp.cs
342 | *.btm.cs
343 | *.odx.cs
344 | *.xsd.cs
345 |
346 | # OpenCover UI analysis results
347 | OpenCover/
348 |
349 | # Azure Stream Analytics local run output
350 | ASALocalRun/
351 |
352 | # MSBuild Binary and Structured Log
353 | *.binlog
354 |
355 | # NVidia Nsight GPU debugger configuration file
356 | *.nvuser
357 |
358 | # MFractors (Xamarin productivity tool) working folder
359 | .mfractor/
360 |
361 | # Local History for Visual Studio
362 | .localhistory/
363 |
364 | # Visual Studio History (VSHistory) files
365 | .vshistory/
366 |
367 | # BeatPulse healthcheck temp database
368 | healthchecksdb
369 |
370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
371 | MigrationBackup/
372 |
373 | # Ionide (cross platform F# VS Code tools) working folder
374 | .ionide/
375 |
376 | # Fody - auto-generated XML schema
377 | FodyWeavers.xsd
378 |
379 | # VS Code files for those working on multiple tools
380 | .vscode/*
381 | !.vscode/settings.json
382 | !.vscode/tasks.json
383 | !.vscode/launch.json
384 | !.vscode/extensions.json
385 | *.code-workspace
386 |
387 | # Local History for Visual Studio Code
388 | .history/
389 |
390 | # Windows Installer files from build outputs
391 | *.cab
392 | *.msi
393 | *.msix
394 | *.msm
395 | *.msp
396 |
397 | # JetBrains Rider
398 | *.sln.iml
399 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Russel Webber
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |   
2 |
3 | # xlDuckDb
4 |
5 | Use DuckDB within Excel with the xlDuckDb addin.
6 |
7 | DuckDB is an amazing tool with deep integration with Python and R, but sometimes you just need data in Excel. xlDuckDb allows DuckDB SQL to be run within Excel. Query results are returned as regular Excel cells.
8 |
9 | 
10 |
11 | # Installation
12 |
13 | Download the xlduckdb.zip file from the latest release on the right.
14 |
15 | Use the "Extract All" option on the right-click menu in Windows Explorer to extract contents of the zip file on your local PC.
16 |
17 | Right-click on the xlDuckDb.xll file and unblock it:
18 |
19 | 
20 |
21 | Double click on the xlDuckDb.xll file to open the addin in Excel. The function _DuckDbQuery_ will be registered automatically.
22 |
23 | ## Requirements
24 |
25 | xlDuckDb only runs on 64-bit Excel 365 released after Sept 2018. The dynamic array resizing functionality is required.
26 |
27 | # Usage
28 |
29 | Any DuckDB SQL can be run and the results will be returned to Excel.
30 |
31 | ### A note on copying queries into Excel
32 |
33 | When copying text such as SQL commands into Excel cells, add a **'** at the start so that Excel treats the input as a string.
34 |
35 | 
36 |
37 | ## Querying JSON files
38 |
39 | Reading data from JSON files can be difficult, particularly if the data is nested.
40 |
41 | This JSON file contains details of Nobel prize laureates:
42 |
43 | 
44 |
45 | The data is presented as a list of dictionaries with the prizes field being a further list of dictionaries.
46 |
47 | DuckDB allows the laureates names and birth countries to be extracted with a simple SQL SELECT. Notice how DuckDB allows the JSON file to be treated just like a regular database table:
48 |
49 | > SELECT firstname, surname, bornCountry FROM 'laureate.json' LIMIT 5
50 |
51 | Passing the SQL into the DuckDbQuery() function gives the following result:
52 |
53 | 
54 |
55 | We can now easily find the countries with the most Nobel prize winners:
56 |
57 | > SELECT bornCountry AS Country, COUNT(\*) AS Number FROM 'laureate.json' GROUP BY bornCountry ORDER BY COUNT(\*) DESC LIMIT 5
58 |
59 | 
60 |
61 | DuckDB supports the use of JSONPath to extract values from nested JSON fields. This allows us to extract the category and motivation of the first prize awarded to each person:
62 |
63 | > SELECT firstname, surname, prizes->>'\$[0].category' AS Category, prizes->>'\$[0].motivation'AS Motivation FROM 'laureate.json' LIMIT 5
64 |
65 | 
66 |
67 | Full details of DuckDB’s JSON capabilities are available in their documentation.
68 |
69 | ## Querying CSV files
70 |
71 | Data is often stored in CSV files. Surprisingly, CSV is not a standardised data format and many variations exist. DuckDB is able to handle most CSV files automatically, detecting the column delimiters, data types, and so on.
72 |
73 | This CSV file contains 10,000 sales records:
74 |
75 | 
76 |
77 | DuckDB allows the region, item type and total revenue for each sale to be extracted with a simple SQL SELECT. Notice how, just like the JSON example above, DuckDB allows the CSV file to be treated as a regular database table:
78 |
79 | > SELECT Region, "Item Type", "Total Revenue" FROM '10000SalesRecords.csv' LIMIT 5
80 |
81 | Passing the SQL into the DuckDbQuery() function gives the following result:
82 |
83 | 
84 |
85 | DuckDB has a user-friendly PIVOT statement that allows us to view the revenues in Asia and Europe broken down by item type:
86 |
87 | > PIVOT '10000SalesRecords.csv' ON Region IN ("Europe", "Asia") USING sum("Total Revenue") GROUP BY "Item Type" ORDER BY "Item Type"
88 |
89 | 
90 |
91 | Data from multiple sources can be combined in a single SQL query. We can combine the JSON and CSV data to show the total sales in the countries with the most Nobel laureates:
92 |
93 | > WITH CountrySales AS
94 | > (SELECT
95 | > CASE WHEN Country='United States of America' THEN 'USA' ELSE Country END AS Country,
96 | > Sum("Total Revenue") AS Sales
97 | > FROM '10000SalesRecords.csv'
98 | > GROUP BY Country)
99 | > SELECT
100 | > cs.Country,
101 | > SUM(cs.Sales) AS Sales,
102 | > COUNT(\*) AS "Nobel Laureates"
103 | > FROM CountrySales cs
104 | > INNER JOIN 'laureate.json' l
105 | > ON cs.Country = l.bornCountry
106 | > GROUP BY cs.Country
107 | > ORDER BY "Nobel Laureates" DESC LIMIT 5
108 |
109 | This SQL uses a common table expression (CTE) to create a CountrySales result set that is joined to the JSON data. Note the use of a CASE expression ensure the country names match in both source data sets:
110 |
111 | 
112 |
113 | Full details of DuckDB’s CSV capabilities are available in their documentation.
114 |
115 | ## Querying Parquet files
116 |
117 | Apache Parquet is an open source, column-oriented data file format designed for efficient data storage and retrieval. It provides efficient data compression and encoding schemes with enhanced performance to handle complex data in bulk.
118 |
119 | DuckDB has extensive support for efficient querying of Parquet files. DuckDB allows very large Parquet datasets to be queried (even data sets that do not fit into memory), multiple files can be queried in parallel.
120 |
121 | The Parquet file, titanic.parquet, contains details of Titanic survivors.
122 |
123 | DuckDB allows the survival status, cabin class, sex and age of the passengers countries to be extracted with a simple SQL SELECT:
124 |
125 | > SELECT Survived, Pclass, Sex, Age FROM 'titanic.parquet' LIMIT 5
126 |
127 | Passing the SQL into the DuckDbQuery() function gives the following result:
128 |
129 | 
130 |
131 | We can now contrast the ages across cabin classes for survivors versus non-survivors:
132 |
133 | > WITH Survivors AS
134 | > (SELECT Pclass, Age, Sex
135 | > FROM 'D:\github\xlslim-code-samples\duckdb\..\data\titanic.parquet'
136 | > WHERE Survived=1)
137 | > PIVOT Survivors ON Pclass USING AVG(Age) GROUP BY Sex
138 |
139 | 
140 |
141 | > WITH Survivors AS
142 | > (SELECT Pclass, Age, Sex
143 | > FROM 'D:\github\xlslim-code-samples\duckdb\..\data\titanic.parquet'
144 | > WHERE Survived=0)
145 | > PIVOT Survivors ON Pclass USING AVG(Age) GROUP BY Sex
146 |
147 | 
148 |
149 | Generally, younger passengers were more likely to survive, with the curious exception of first class female passengers.
150 |
151 | Full details of DuckDB’s Parquet capabilities are available in their documentation.
152 |
153 | ## Querying from remote locations
154 |
155 | DuckDB has functionality to directly query data located on https and in AWS S3.
156 |
157 | As an example, the holdings.parquet file can be queried from https://duckdb.org:
158 |
159 | > SELECT \* FROM 'https://duckdb.org/data/holdings.parquet';
160 |
161 | 
162 |
163 | Similarly we can attach to the DuckDB stations database in S3 and query the number of stations in each country:
164 |
165 | 
166 |
167 | Access to AWS S3 data usually requires credentials. See the DuckDB S3 API documentation for details about how to use secrets to provide credentials to S3.
168 |
169 | Hopefully this gives a sense of the power of DuckDB! Please read the [DuckDB documentation](https://duckdb.org/docs/) for more information about DuckDB’s capabilities, including how to attach to SQLite, Postgress or indeed any ODBC databases.
170 |
171 | ## Why .Net Framework 4.8
172 |
173 | xlDuckDb targets .Net Framework 4.8 which may seem an odd choice. This is done to keep the Excel addin as compatible as possible. Govert van Drimmelen (the ExcelDNA founder) advises targeting .Net Framework for addins for the foreseeable future, see his full [post](https://groups.google.com/g/exceldna/c/MlYMIhrm468/m/6TJdYbBIBAAJ)
174 |
175 | ## Thanks
176 |
177 | xlDuckDb would not have been possible without the [ExcelDNA](https://github.com/excel-dna) and [DuckDB.NET](https://github.com/Giorgi/DuckDB.NET) projects.
178 |
179 | The DuckDB query in Excel functionality was extracted from the commercial product [xlSlim](https://www.xlslim.com). If you need the same DuckDB functionality within a Digicert EV certificate signed addin then xlSlim is what you are looking for. xlSlim has a Windows .msi installer and is installable from [Chocolatey](https://community.chocolatey.org/packages/xlslim/). xlSlim's main purpose is to run Python functions within Excel as easily as possible.
180 |
--------------------------------------------------------------------------------
/UnitTests/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | [assembly: AssemblyTitle("UnitTests")]
6 | [assembly: AssemblyDescription("")]
7 | [assembly: AssemblyConfiguration("")]
8 | [assembly: AssemblyCompany("")]
9 | [assembly: AssemblyProduct("UnitTests")]
10 | [assembly: AssemblyCopyright("Copyright © 2024")]
11 | [assembly: AssemblyTrademark("")]
12 | [assembly: AssemblyCulture("")]
13 |
14 | [assembly: ComVisible(false)]
15 |
16 | [assembly: Guid("b440ad88-9801-424f-9e63-d1fefcbce6da")]
17 |
18 | // [assembly: AssemblyVersion("1.0.*")]
19 | [assembly: AssemblyVersion("1.0.0.0")]
20 | [assembly: AssemblyFileVersion("1.0.0.0")]
21 |
--------------------------------------------------------------------------------
/UnitTests/UnitTestDuckDbQueries.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using ExcelDna.Integration;
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using xlDuckDb;
5 |
6 | namespace UnitTests
7 | {
8 | [TestClass]
9 | public class UnitTestDuckDbQueries
10 | {
11 | [TestMethod]
12 | public void TestHttpParquetRead()
13 | {
14 | var data = DuckDbHelper.ExecuteQuery("SELECT * FROM 'https://duckdb.org/data/holdings.parquet'");
15 | Assert.IsNotNull(data);
16 | }
17 |
18 | [TestMethod]
19 | public void TestBigInt()
20 | {
21 | var data = DuckDbHelper.ExecuteQuery("SELECT 1::BIGINT");
22 | Assert.IsNotNull(data);
23 | Assert.IsInstanceOfType(data[1, 0], typeof(long));
24 | Assert.AreEqual(1L, data[1, 0]);
25 | }
26 |
27 | [TestMethod]
28 | public void TestNullBigInt()
29 | {
30 | var data = DuckDbHelper.ExecuteQuery("SELECT NULL::BIGINT");
31 | Assert.IsNotNull(data);
32 | Assert.IsInstanceOfType(data[1, 0], typeof(ExcelError));
33 | Assert.AreEqual(ExcelError.ExcelErrorNA, data[1, 0]);
34 | }
35 |
36 | [TestMethod]
37 | public void TestUBigInt()
38 | {
39 | var data = DuckDbHelper.ExecuteQuery("SELECT 1::UBIGINT");
40 | Assert.IsNotNull(data);
41 | Assert.IsInstanceOfType(data[1, 0], typeof(long));
42 | Assert.AreEqual(1L, data[1, 0]);
43 | }
44 |
45 | [TestMethod]
46 | public void TestBit()
47 | {
48 | var data = DuckDbHelper.ExecuteQuery("SELECT bitstring('0101011', 12)");
49 | Assert.IsNotNull(data);
50 | Assert.IsInstanceOfType(data[1, 0], typeof(string));
51 | Assert.AreEqual("000000101011", data[1, 0]);
52 | }
53 |
54 | [TestMethod]
55 | public void TestBlob()
56 | {
57 | var data = DuckDbHelper.ExecuteQuery("SELECT encode('my_string_with_ü')");
58 | Assert.IsNotNull(data);
59 | Assert.IsInstanceOfType(data[1, 0], typeof(string));
60 | Assert.AreEqual("my_string_with_ü", data[1, 0]);
61 | }
62 |
63 | [TestMethod]
64 | public void TestBoolean()
65 | {
66 | var data = DuckDbHelper.ExecuteQuery("SELECT true, false");
67 | Assert.IsNotNull(data);
68 | Assert.IsInstanceOfType(data[1, 0], typeof(bool));
69 | Assert.IsInstanceOfType(data[1, 1], typeof(bool));
70 | Assert.AreEqual(true, data[1, 0]);
71 | Assert.AreEqual(false, data[1, 1]);
72 | }
73 |
74 | [TestMethod]
75 | public void TestDate()
76 | {
77 | var data = DuckDbHelper.ExecuteQuery("SELECT make_date(1992, 9, 20)");
78 | Assert.IsNotNull(data);
79 | Assert.IsInstanceOfType(data[1, 0], typeof(DateTime));
80 | Assert.AreEqual(new DateTime(1992, 9, 20), data[1, 0]);
81 | }
82 |
83 | [TestMethod]
84 | public void TestDecimal()
85 | {
86 | var data = DuckDbHelper.ExecuteQuery("SELECT 123.45678::DECIMAL(8, 2)");
87 | Assert.IsNotNull(data);
88 | Assert.IsInstanceOfType(data[1, 0], typeof(double));
89 | Assert.AreEqual(123.46, data[1, 0]);
90 | }
91 |
92 | [TestMethod]
93 | public void TestDouble()
94 | {
95 | var data = DuckDbHelper.ExecuteQuery("SELECT 123.45678::DOUBLE");
96 | Assert.IsNotNull(data);
97 | Assert.IsInstanceOfType(data[1, 0], typeof(double));
98 | Assert.AreEqual(123.45678, data[1, 0]);
99 | }
100 |
101 | [TestMethod]
102 | public void TestNanDouble()
103 | {
104 | var data = DuckDbHelper.ExecuteQuery("SELECT 1.0/0::DOUBLE");
105 | Assert.IsNotNull(data);
106 | Assert.IsInstanceOfType(data[1, 0], typeof(ExcelError));
107 | Assert.AreEqual(ExcelError.ExcelErrorNum, data[1, 0]);
108 | }
109 |
110 | [TestMethod]
111 | public void TestFloat()
112 | {
113 | var data = DuckDbHelper.ExecuteQuery("SELECT 123.45678::FLOAT");
114 | Assert.IsNotNull(data);
115 | Assert.IsInstanceOfType(data[1, 0], typeof(double));
116 | Assert.IsTrue(123.45677947998 - (double) data[1, 0] < 1e-5);
117 | }
118 |
119 | [TestMethod]
120 | public void TestHugeInt()
121 | {
122 | var data = DuckDbHelper.ExecuteQuery("SELECT 1::HUGEINT");
123 | Assert.IsNotNull(data);
124 | Assert.IsInstanceOfType(data[1, 0], typeof(long));
125 | Assert.AreEqual(1L, data[1, 0]);
126 | }
127 |
128 | [TestMethod]
129 | public void TestUHugeInt()
130 | {
131 | var data = DuckDbHelper.ExecuteQuery("SELECT 1::UHUGEINT");
132 | Assert.IsNotNull(data);
133 | Assert.IsInstanceOfType(data[1, 0], typeof(long));
134 | Assert.AreEqual(1L, data[1, 0]);
135 | }
136 |
137 |
138 | [TestMethod]
139 | public void TestInteger()
140 | {
141 | var data = DuckDbHelper.ExecuteQuery("SELECT 1::INT");
142 | Assert.IsNotNull(data);
143 | Assert.IsInstanceOfType(data[1, 0], typeof(int));
144 | Assert.AreEqual(1, data[1, 0]);
145 | }
146 |
147 | [TestMethod]
148 | public void TestUInteger()
149 | {
150 | var data = DuckDbHelper.ExecuteQuery("SELECT 1::UINTEGER");
151 | Assert.IsNotNull(data);
152 | Assert.IsInstanceOfType(data[1, 0], typeof(uint));
153 | Assert.AreEqual((uint) 1, data[1, 0]);
154 | }
155 |
156 | [TestMethod]
157 | public void TestIntervalFromTime()
158 | {
159 | var now = DateTime.Now;
160 | var data = DuckDbHelper.ExecuteQuery("SELECT current_localtime()");
161 | Assert.IsNotNull(data);
162 | Assert.IsInstanceOfType(data[1, 0], typeof(DateTime));
163 | Assert.AreEqual(now.Hour, ((DateTime) data[1, 0]).Hour);
164 | Assert.AreEqual(now.Minute, ((DateTime) data[1, 0]).Minute);
165 | Assert.AreEqual(now.Second, ((DateTime) data[1, 0]).Second);
166 | }
167 |
168 | [TestMethod]
169 | public void TestIntervalFromFunc()
170 | {
171 | var data = DuckDbHelper.ExecuteQuery("SELECT to_days(10)");
172 | Assert.IsNotNull(data);
173 | Assert.IsInstanceOfType(data[1, 0], typeof(DateTime));
174 | Assert.IsTrue(Math.Abs(((DateTime) data[1, 0] - new DateTime(1899, 12, 30)).TotalDays - 10) < 0.01);
175 | }
176 |
177 | [TestMethod]
178 | public void TestJson()
179 | {
180 | var data = DuckDbHelper.ExecuteQuery("SELECT '{\"duck\": 42}'::JSON");
181 | Assert.IsNotNull(data);
182 | Assert.IsInstanceOfType(data[1, 0], typeof(string));
183 | Assert.AreEqual("{\"duck\": 42}", data[1, 0]);
184 | }
185 |
186 | [TestMethod]
187 | public void TestSmallInteger()
188 | {
189 | var data = DuckDbHelper.ExecuteQuery("SELECT 1::SMALLINT");
190 | Assert.IsNotNull(data);
191 | Assert.IsInstanceOfType(data[1, 0], typeof(short));
192 | Assert.AreEqual((short) 1, data[1, 0]);
193 | }
194 |
195 | [TestMethod]
196 | public void TestUSmallInteger()
197 | {
198 | var data = DuckDbHelper.ExecuteQuery("SELECT 1::USMALLINT");
199 | Assert.IsNotNull(data);
200 | Assert.IsInstanceOfType(data[1, 0], typeof(ushort));
201 | Assert.AreEqual((ushort) 1, data[1, 0]);
202 | }
203 |
204 | [TestMethod]
205 | public void TestTime()
206 | {
207 | var data = DuckDbHelper.ExecuteQuery("SELECT TIME '1992-09-20 11:30:00.123456'");
208 | Assert.IsNotNull(data);
209 | Assert.IsInstanceOfType(data[1, 0], typeof(DateTime));
210 | Assert.IsTrue(((DateTime) data[1, 0] - new DateTime(1899, 12, 30)).Hours == 11);
211 | Assert.IsTrue(((DateTime) data[1, 0] - new DateTime(1899, 12, 30)).Minutes == 30);
212 | }
213 |
214 | [TestMethod]
215 | public void TestTimeTz()
216 | {
217 | var data = DuckDbHelper.ExecuteQuery("SELECT TIMETZ '1992-09-20 11:30:00.123456+05:30'");
218 | Assert.IsNotNull(data);
219 | Assert.IsInstanceOfType(data[1, 0], typeof(DateTime));
220 | Assert.IsTrue(((DateTime) data[1, 0] - new DateTime(1899, 12, 30)).Hours == 6);
221 | Assert.IsTrue(((DateTime) data[1, 0] - new DateTime(1899, 12, 30)).Minutes == 0);
222 | }
223 |
224 | [TestMethod]
225 | public void TestTimeStamp()
226 | {
227 | var data = DuckDbHelper.ExecuteQuery("SELECT TIMESTAMP '1992-09-20 11:30:00';");
228 | Assert.IsNotNull(data);
229 | Assert.IsInstanceOfType(data[1, 0], typeof(DateTime));
230 | Assert.AreEqual(new DateTime(1992, 9, 20, 11, 30, 0), data[1, 0]);
231 | }
232 |
233 | [TestMethod]
234 | public void TestTimeStampTz()
235 | {
236 | var data = DuckDbHelper.ExecuteQuery("SELECT TIMESTAMPTZ '1992-09-20 11:30:00+01:00';");
237 | Assert.IsNotNull(data);
238 | Assert.IsInstanceOfType(data[1, 0], typeof(DateTime));
239 | Assert.AreEqual(new DateTime(1992, 9, 20, 10, 30, 0), data[1, 0]);
240 | }
241 |
242 | [TestMethod]
243 | public void TestTinyInteger()
244 | {
245 | var data = DuckDbHelper.ExecuteQuery("SELECT 1::TINYINT");
246 | Assert.IsNotNull(data);
247 | Assert.IsInstanceOfType(data[1, 0], typeof(sbyte));
248 | Assert.AreEqual((sbyte) 1, data[1, 0]);
249 | }
250 |
251 | [TestMethod]
252 | public void TestUTinyInteger()
253 | {
254 | var data = DuckDbHelper.ExecuteQuery("SELECT 1::UTINYINT");
255 | Assert.IsNotNull(data);
256 | Assert.IsInstanceOfType(data[1, 0], typeof(byte));
257 | Assert.AreEqual((byte) 1, data[1, 0]);
258 | }
259 |
260 | [TestMethod]
261 | public void TestUuid()
262 | {
263 | var data = DuckDbHelper.ExecuteQuery("SELECT 'eeccb8c5-9943-b2bb-bb5e-222f4e14b687'::UUID");
264 | Assert.IsNotNull(data);
265 | Assert.IsInstanceOfType(data[1, 0], typeof(string));
266 | Assert.AreEqual("eeccb8c5-9943-b2bb-bb5e-222f4e14b687", data[1, 0]);
267 | }
268 |
269 | [TestMethod]
270 | public void TestVarChar()
271 | {
272 | var data = DuckDbHelper.ExecuteQuery("SELECT 'Hello World'::VARCHAR");
273 | Assert.IsNotNull(data);
274 | Assert.IsInstanceOfType(data[1, 0], typeof(string));
275 | Assert.AreEqual("Hello World", data[1, 0]);
276 | }
277 |
278 | [TestMethod]
279 | public void TestNullVarchar()
280 | {
281 | var data = DuckDbHelper.ExecuteQuery("SELECT NULL::VARCHAR");
282 | Assert.IsNotNull(data);
283 | Assert.IsInstanceOfType(data[1, 0], typeof(ExcelError));
284 | Assert.AreEqual(ExcelError.ExcelErrorNA, data[1, 0]);
285 | }
286 | }
287 | }
--------------------------------------------------------------------------------
/UnitTests/UnitTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Debug
10 | AnyCPU
11 | {B440AD88-9801-424F-9E63-D1FEFCBCE6DA}
12 | Library
13 | Properties
14 | UnitTests
15 | UnitTests
16 | v4.8
17 | 512
18 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
19 | 15.0
20 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
21 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages
22 | False
23 | UnitTest
24 |
25 |
26 |
27 |
28 | true
29 |
30 |
31 | true
32 | bin\x64\Debug\
33 | DEBUG;TRACE
34 | full
35 | x64
36 | 7.3
37 | prompt
38 |
39 |
40 | bin\x64\Release\
41 | TRACE
42 | true
43 | pdbonly
44 | x64
45 | 7.3
46 | prompt
47 |
48 |
49 |
50 | ..\packages\DuckDB.NET.Bindings.Full.1.2.1\lib\netstandard2.0\DuckDB.NET.Bindings.dll
51 |
52 |
53 | ..\packages\DuckDB.NET.Data.Full.1.2.1\lib\netstandard2.0\DuckDB.NET.Data.dll
54 |
55 |
56 | ..\packages\ExcelDna.Integration.1.8.0\lib\net452\ExcelDna.Integration.dll
57 |
58 |
59 | ..\packages\Microsoft.ApplicationInsights.2.23.0\lib\net46\Microsoft.ApplicationInsights.dll
60 |
61 |
62 |
63 | ..\packages\Microsoft.Testing.Platform.MSBuild.1.6.3\lib\netstandard2.0\Microsoft.Testing.Extensions.MSBuild.dll
64 |
65 |
66 | ..\packages\Microsoft.Testing.Extensions.Telemetry.1.6.3\lib\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.dll
67 |
68 |
69 | ..\packages\Microsoft.Testing.Extensions.TrxReport.Abstractions.1.6.3\lib\netstandard2.0\Microsoft.Testing.Extensions.TrxReport.Abstractions.dll
70 |
71 |
72 | ..\packages\Microsoft.Testing.Extensions.VSTestBridge.1.6.3\lib\netstandard2.0\Microsoft.Testing.Extensions.VSTestBridge.dll
73 |
74 |
75 | ..\packages\Microsoft.Testing.Platform.1.6.3\lib\netstandard2.0\Microsoft.Testing.Platform.dll
76 |
77 |
78 | ..\packages\Microsoft.TestPlatform.ObjectModel.17.13.0\lib\net462\Microsoft.TestPlatform.CoreUtilities.dll
79 |
80 |
81 | ..\packages\Microsoft.TestPlatform.ObjectModel.17.13.0\lib\net462\Microsoft.TestPlatform.PlatformAbstractions.dll
82 |
83 |
84 | ..\packages\Microsoft.TestPlatform.ObjectModel.17.13.0\lib\net462\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll
85 |
86 |
87 | ..\packages\MSTest.TestFramework.3.8.3\lib\net462\Microsoft.VisualStudio.TestPlatform.TestFramework.dll
88 |
89 |
90 | ..\packages\MSTest.TestFramework.3.8.3\lib\net462\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll
91 |
92 |
93 |
94 | ..\packages\System.Buffers.4.6.1\lib\net462\System.Buffers.dll
95 |
96 |
97 | ..\packages\System.Collections.Immutable.9.0.4\lib\net462\System.Collections.Immutable.dll
98 |
99 |
100 |
101 |
102 | ..\packages\System.Diagnostics.DiagnosticSource.9.0.4\lib\net462\System.Diagnostics.DiagnosticSource.dll
103 |
104 |
105 | ..\packages\System.Memory.4.6.3\lib\net462\System.Memory.dll
106 |
107 |
108 |
109 |
110 | ..\packages\System.Numerics.Vectors.4.6.1\lib\net462\System.Numerics.Vectors.dll
111 |
112 |
113 | ..\packages\System.Reflection.Metadata.9.0.4\lib\net462\System.Reflection.Metadata.dll
114 |
115 |
116 |
117 | ..\packages\System.Runtime.CompilerServices.Unsafe.6.1.2\lib\net462\System.Runtime.CompilerServices.Unsafe.dll
118 |
119 |
120 |
121 | ..\packages\System.Threading.Tasks.Extensions.4.6.3\lib\net462\System.Threading.Tasks.Extensions.dll
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 | {e86689c7-a944-46f0-a91d-4306c9813bf2}
136 | xlDuckDb
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 | xcopy "$(SolutionDir)packages\DuckDB.NET.Bindings.Full.1.2.1\runtimes\win-x64\native\duckdb.dll" . /Y
148 |
149 |
150 |
151 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
--------------------------------------------------------------------------------
/UnitTests/app.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/UnitTests/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/images/duckdb_http_parquet.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RusselWebber/xlDuckDb/5253296d84974c9300c19a2ffd16131b9427bc98/images/duckdb_http_parquet.gif
--------------------------------------------------------------------------------
/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RusselWebber/xlDuckDb/5253296d84974c9300c19a2ffd16131b9427bc98/images/logo.png
--------------------------------------------------------------------------------
/images/logo_rect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RusselWebber/xlDuckDb/5253296d84974c9300c19a2ffd16131b9427bc98/images/logo_rect.png
--------------------------------------------------------------------------------
/images/xlduckdb_unblock.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RusselWebber/xlDuckDb/5253296d84974c9300c19a2ffd16131b9427bc98/images/xlduckdb_unblock.gif
--------------------------------------------------------------------------------
/xlDuckDb.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.12.35506.116
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "xlDuckDb", "xlDuckDb\xlDuckDb.csproj", "{E86689C7-A944-46F0-A91D-4306C9813BF2}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "UnitTests\UnitTests.csproj", "{B440AD88-9801-424F-9E63-D1FEFCBCE6DA}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|x64 = Debug|x64
13 | Release|x64 = Release|x64
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {E86689C7-A944-46F0-A91D-4306C9813BF2}.Debug|x64.ActiveCfg = Debug|x64
17 | {E86689C7-A944-46F0-A91D-4306C9813BF2}.Debug|x64.Build.0 = Debug|x64
18 | {E86689C7-A944-46F0-A91D-4306C9813BF2}.Release|x64.ActiveCfg = Release|x64
19 | {E86689C7-A944-46F0-A91D-4306C9813BF2}.Release|x64.Build.0 = Release|x64
20 | {B440AD88-9801-424F-9E63-D1FEFCBCE6DA}.Debug|x64.ActiveCfg = Debug|x64
21 | {B440AD88-9801-424F-9E63-D1FEFCBCE6DA}.Debug|x64.Build.0 = Debug|x64
22 | {B440AD88-9801-424F-9E63-D1FEFCBCE6DA}.Release|x64.ActiveCfg = Release|x64
23 | {B440AD88-9801-424F-9E63-D1FEFCBCE6DA}.Release|x64.Build.0 = Release|x64
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | EndGlobal
29 |
--------------------------------------------------------------------------------
/xlDuckDb/ArrayHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace xlDuckDb
5 | {
6 | internal static class ArrayHelper
7 | {
8 | internal static T[,] AsMultiDimensionalArray(this IList arrays)
9 | {
10 | if (arrays == null) throw new ArgumentNullException(nameof(arrays));
11 | if (arrays.Count == 0) throw new ArgumentException("Value cannot be an empty collection.", nameof(arrays));
12 |
13 | var minorLength = arrays[0].Length;
14 | var ret = new T[arrays.Count, minorLength];
15 | for (var i = 0; i < arrays.Count; i++)
16 | {
17 | var array = arrays[i];
18 | if (array.Length != minorLength)
19 | throw new ArgumentException
20 | ("All arrays must be the same length");
21 | for (var j = 0; j < minorLength; j++) ret[i, j] = array[j];
22 | }
23 |
24 | return ret;
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/xlDuckDb/DuckDbHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Numerics;
5 | using System.Text;
6 | using DuckDB.NET.Data;
7 | using DuckDB.NET.Native;
8 | using ExcelDna.Integration;
9 |
10 | namespace xlDuckDb
11 | {
12 | public static class DuckDbHelper
13 | {
14 | public static object[,] ExecuteQuery(string query, string dataSource = null)
15 | {
16 | if (string.IsNullOrEmpty(query))
17 | throw new ArgumentException("Value cannot be null or empty.", nameof(query));
18 |
19 | using (var duckDbConnection =
20 | new DuckDBConnection(
21 | $"Data Source = {(string.IsNullOrEmpty(dataSource) ? ":memory:" : dataSource)}"))
22 | {
23 | duckDbConnection.Open();
24 |
25 | using (var command = duckDbConnection.CreateCommand())
26 | {
27 | command.CommandText = query;
28 | using (var reader = command.ExecuteReader())
29 | {
30 | if (!reader.HasRows || reader.IsClosed) return new object[,] {{null}};
31 | var rows = new List