This is an automated email from the ASF dual-hosted git repository.
jiayu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/sedona-db.git
The following commit(s) were added to refs/heads/main by this push:
new e2b3627 feat(r/sedonadb): Add R bindings (#23)
e2b3627 is described below
commit e2b3627ac4c21a96be625b456586d44fecd18fd4
Author: Dewey Dunnington <[email protected]>
AuthorDate: Wed Sep 10 21:28:42 2025 +0000
feat(r/sedonadb): Add R bindings (#23)
* import files
* update static lib name
* pre-commit
* first pass at licensing
* license R files
* license the rust folder
* formatting
* maybe with ci job
* try macos
* fix compile
* fix readme
* pre-commit
* Update r/sedonadb/src/rust/src/lib.rs
Co-authored-by: Copilot <[email protected]>
* remove wraper code
---------
Co-authored-by: Copilot <[email protected]>
---
.github/workflows/r.yml | 94 ++++++++
.gitignore | 3 +
Cargo.lock | 60 +++++
Cargo.toml | 1 +
ci/scripts/rat/license_ignore.txt | 6 +
r/sedonadb/.Rbuildignore | 12 +
.gitignore => r/sedonadb/.gitignore | 35 +--
r/sedonadb/DESCRIPTION | 23 ++
r/sedonadb/LICENSE.md | 194 ++++++++++++++++
r/sedonadb/NAMESPACE | 33 +++
r/sedonadb/R/000-wrappers.R | 219 ++++++++++++++++++
.gitignore => r/sedonadb/R/adbc.R | 48 ++--
r/sedonadb/R/context.R | 95 ++++++++
r/sedonadb/R/dataframe.R | 249 +++++++++++++++++++++
.gitignore => r/sedonadb/R/pkg-sf.R | 48 ++--
.gitignore => r/sedonadb/R/sedonadb-package.R | 33 +--
r/sedonadb/R/zzz.R | 115 ++++++++++
r/sedonadb/README.Rmd | 88 ++++++++
r/sedonadb/README.md | 120 ++++++++++
.gitignore => r/sedonadb/cleanup | 29 +--
.gitignore => r/sedonadb/cleanup.win | 29 +--
r/sedonadb/configure | 91 ++++++++
r/sedonadb/configure.win | 60 +++++
.../inst/files/natural-earth_cities_geo.parquet | Bin 0 -> 17322 bytes
.../natural-earth_countries-geography_geo.parquet | Bin 0 -> 181446 bytes
.../inst/files/natural-earth_countries_geo.parquet | Bin 0 -> 186590 bytes
r/sedonadb/man/as_sedonadb_dataframe.Rd | 25 +++
.../man/figures/README-unnamed-chunk-3-1.png | Bin 0 -> 344965 bytes
r/sedonadb/man/sd_compute.Rd | 29 +++
r/sedonadb/man/sd_count.Rd | 21 ++
r/sedonadb/man/sd_drop_view.Rd | 27 +++
r/sedonadb/man/sd_preview.Rd | 32 +++
r/sedonadb/man/sd_read_parquet.Rd | 22 ++
r/sedonadb/man/sd_sql.Rd | 21 ++
r/sedonadb/man/sd_to_view.Rd | 27 +++
r/sedonadb/man/sedonadb-package.Rd | 15 ++
r/sedonadb/man/sedonadb_adbc.Rd | 26 +++
r/sedonadb/sedonadb.Rproj | 22 ++
.gitignore => r/sedonadb/src/.gitignore | 33 +--
r/sedonadb/src/Makevars.in | 60 +++++
r/sedonadb/src/Makevars.win.in | 41 ++++
r/sedonadb/src/init.c | 201 +++++++++++++++++
r/sedonadb/src/rust/.cargo/config.toml | 10 +
.gitignore => r/sedonadb/src/rust/Cargo.toml | 50 ++---
r/sedonadb/src/rust/api.h | 46 ++++
r/sedonadb/src/rust/src/context.rs | 124 ++++++++++
r/sedonadb/src/rust/src/dataframe.rs | 194 ++++++++++++++++
r/sedonadb/src/rust/src/error.rs | 58 +++++
r/sedonadb/src/rust/src/lib.rs | 42 ++++
r/sedonadb/src/rust/src/runtime.rs | 153 +++++++++++++
r/sedonadb/src/sedonadb-win.def | 2 +
.gitignore => r/sedonadb/tests/testthat.R | 37 +--
.../sedonadb/tests/testthat/test-adbc.R | 42 ++--
.../sedonadb/tests/testthat/test-context.R | 52 ++---
r/sedonadb/tests/testthat/test-dataframe.R | 137 ++++++++++++
55 files changed, 2929 insertions(+), 305 deletions(-)
diff --git a/.github/workflows/r.yml b/.github/workflows/r.yml
new file mode 100644
index 0000000..3fdf161
--- /dev/null
+++ b/.github/workflows/r.yml
@@ -0,0 +1,94 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+name: r
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ branches:
+ - main
+ paths:
+ - 'Cargo.toml'
+ - 'Cargo.lock'
+ - '.github/workflows/r.yml'
+ - 'rust/**'
+ - 'c/**'
+ - 'r/**'
+jobs:
+
+ test:
+ runs-on: ${{ matrix.config.os }}
+ name: ${{ matrix.config.os }} (${{ matrix.config.r }})
+
+ strategy:
+ fail-fast: false
+ matrix:
+ config:
+ - {os: ubuntu-latest, r: 'release'}
+ - {os: macos-latest, r: 'release'}
+
+ env:
+ GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
+ R_KEEP_PKG_SOURCE: yes
+
+ steps:
+ - uses: actions/checkout@v4
+ - uses: r-lib/actions/setup-pandoc@v2
+ - uses: r-lib/actions/setup-r@v2
+ with:
+ r-version: ${{ matrix.config.r }}
+ http-user-agent: ${{ matrix.config.http-user-agent }}
+ use-public-rspm: true
+
+ - name: Use stable Rust
+ id: rust
+ run: |
+ rustup toolchain install stable --no-self-update
+ rustup default stable
+
+ - name: Install dependencies (Linux)
+ if: matrix.config.os == 'ubuntu-latest'
+ run: sudo apt-get update && sudo apt-get install -y libgeos-dev
+
+ - name: Install dependencies (MacOS)
+ if: matrix.config.os == 'macos-latest'
+ run: brew install geos
+
+ - uses: Swatinem/rust-cache@v2
+ with:
+ # Update this key to force a new cache
+ prefix-key: "r-v1"
+
+ - name: Install build dependencies
+ run: |
+ R -e 'install.packages(c("nanoarrow", "geoarrow"))'
+
+ - name: Install R Package
+ run: |
+ R CMD INSTALL r/sedonadb --preclean
+
+ - uses: r-lib/actions/setup-r-dependencies@v2
+ with:
+ needs: check
+ working-directory: r/sedonadb
+
+ - name: Test R Package
+ run: |
+ R -e 'library(testthat); test_local("r/sedonadb",
MultiReporter$new(list(CheckReporter$new(), LocationReporter$new())))'
diff --git a/.gitignore b/.gitignore
index 7441737..3ef7d21 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,3 +43,6 @@ site/
# Python cache files
__pycache__
+
+# R-related files
+.Rproj.user
diff --git a/Cargo.lock b/Cargo.lock
index 21d1617..5a975d9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4694,6 +4694,47 @@ dependencies = [
"winapi-util",
]
+[[package]]
+name = "savvy"
+version = "0.8.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b0b77a8477c7edc3166bdebc6f8fc449967942aa3d25f22b13a336df29da3e2"
+dependencies = [
+ "cc",
+ "rustversion",
+ "savvy-ffi",
+ "savvy-macro",
+]
+
+[[package]]
+name = "savvy-bindgen"
+version = "0.8.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a29c423a6e85d4d7c4172db33ace022acea97b2c6fded04d28da50a14591c87d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "savvy-ffi"
+version = "0.8.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e06cec12411cc3a0cc7eaa13601c431f770cbc89825576fea8aebc369b3ed41c"
+
+[[package]]
+name = "savvy-macro"
+version = "0.8.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df4c38f496dd2a8f5aac0bdff321e88f59d241fc4cdf1b75f4859a69134c020f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "savvy-bindgen",
+ "syn 2.0.106",
+]
+
[[package]]
name = "schannel"
version = "0.1.28"
@@ -5141,6 +5182,25 @@ dependencies = [
"tokio",
]
+[[package]]
+name = "sedonadbr"
+version = "0.1.0"
+dependencies = [
+ "arrow-array",
+ "arrow-schema",
+ "datafusion",
+ "datafusion-common",
+ "savvy",
+ "savvy-ffi",
+ "sedona",
+ "sedona-adbc",
+ "sedona-expr",
+ "sedona-geoparquet",
+ "sedona-schema",
+ "thiserror 2.0.16",
+ "tokio",
+]
+
[[package]]
name = "semver"
version = "1.0.26"
diff --git a/Cargo.toml b/Cargo.toml
index 4797430..0a8e803 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -21,6 +21,7 @@ members = [
"c/sedona-proj",
"c/sedona-s2geography",
"c/sedona-tg",
+ "r/sedonadb/src/rust",
"rust/sedona-adbc",
"rust/sedona-expr",
"rust/sedona-functions",
diff --git a/ci/scripts/rat/license_ignore.txt
b/ci/scripts/rat/license_ignore.txt
index a3b26d0..daf8efe 100644
--- a/ci/scripts/rat/license_ignore.txt
+++ b/ci/scripts/rat/license_ignore.txt
@@ -1,5 +1,8 @@
+.Rbuildignore
*.ipynb
*.json
+*.Rd
+*.Rproj
*.svg
c/sedona-geoarrow-c/src/geoarrow/*
c/sedona-geoarrow-c/src/geoarrow/ryu/*
@@ -9,5 +12,8 @@ c/sedona-s2geography/s2geometry/*
c/sedona-tg/src/tg/*
Cargo.lock
ci/scripts/rat/license_*.txt
+DESCRIPTION
LICENSE
+NAMESPACE
+sedonadb-win.def
submodules/geoarrow-data/*
diff --git a/r/sedonadb/.Rbuildignore b/r/sedonadb/.Rbuildignore
new file mode 100644
index 0000000..7e7df63
--- /dev/null
+++ b/r/sedonadb/.Rbuildignore
@@ -0,0 +1,12 @@
+^sedonadb\.Rproj$
+^\.Rproj\.user$
+^LICENSE\.md$
+
+^src/rust/\.cargo$
+^src/rust/target$
+^src/Makevars$
+^src/Makevars\.win$
+^\.cache$
+^compile_commands\.json$
+^\.vscode$
+^README\.Rmd$
diff --git a/.gitignore b/r/sedonadb/.gitignore
similarity index 70%
copy from .gitignore
copy to r/sedonadb/.gitignore
index 7441737..a440e54 100644
--- a/.gitignore
+++ b/r/sedonadb/.gitignore
@@ -15,31 +15,12 @@
# specific language governing permissions and limitations
# under the License.
-# MacOS
+.Rproj.user
+.Rhistory
+.Rdata
+.httr-oauth
.DS_Store
-
-# sedona-cli
-.history
-
-# Generated by cargo
-debug/
-target/
-
-# Generated by rustfmt
-**/*.rs.bk
-
-# Generated by cargo llvm-cov
-coverage/
-
-# MSVC Windows builds of rustc generate these, which store debugging
information
-*.pdb
-
-# IDE-specific settings
-.idea/
-.vscode/
-
-# documentation
-site/
-
-# Python cache files
-__pycache__
+.quarto
+.cache
+compile_commands.json
+.vscode
diff --git a/r/sedonadb/DESCRIPTION b/r/sedonadb/DESCRIPTION
new file mode 100644
index 0000000..5167391
--- /dev/null
+++ b/r/sedonadb/DESCRIPTION
@@ -0,0 +1,23 @@
+Package: sedonadb
+Title: Bindings for Apache SedonaDB
+Version: 0.0.0.9000
+Authors@R:
+ person("Dewey", "Dunnington", , "[email protected]", role = c("aut",
"cre"))
+Description: Provides bindings for Apache SedonaDB, a lightweight
+ query engine optimized for spatial workflows.
+License: Apache License (>= 2)
+Encoding: UTF-8
+Roxygen: list(markdown = TRUE)
+RoxygenNote: 7.3.2
+SystemRequirements: Cargo (Rust's package manager), rustc
+Depends: R (>= 4.1.0)
+Suggests:
+ adbcdrivermanager,
+ rlang,
+ testthat (>= 3.0.0),
+ withr,
+ wk
+Config/testthat/edition: 3
+Imports:
+ geoarrow,
+ nanoarrow
diff --git a/r/sedonadb/LICENSE.md b/r/sedonadb/LICENSE.md
new file mode 100644
index 0000000..b62a9b5
--- /dev/null
+++ b/r/sedonadb/LICENSE.md
@@ -0,0 +1,194 @@
+Apache License
+==============
+
+_Version 2.0, January 2004_
+_<<http://www.apache.org/licenses/>>_
+
+### Terms and Conditions for use, reproduction, and distribution
+
+#### 1. Definitions
+
+“License” shall mean the terms and conditions for use, reproduction, and
+distribution as defined by Sections 1 through 9 of this document.
+
+“Licensor” shall mean the copyright owner or entity authorized by the copyright
+owner that is granting the License.
+
+“Legal Entity” shall mean the union of the acting entity and all other entities
+that control, are controlled by, or are under common control with that entity.
+For the purposes of this definition, “control” means **(i)** the power, direct
or
+indirect, to cause the direction or management of such entity, whether by
+contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of
the
+outstanding shares, or **(iii)** beneficial ownership of such entity.
+
+“You” (or “Your”) shall mean an individual or Legal Entity exercising
+permissions granted by this License.
+
+“Source” form shall mean the preferred form for making modifications, including
+but not limited to software source code, documentation source, and
configuration
+files.
+
+“Object” form shall mean any form resulting from mechanical transformation or
+translation of a Source form, including but not limited to compiled object
code,
+generated documentation, and conversions to other media types.
+
+“Work” shall mean the work of authorship, whether in Source or Object form,
made
+available under the License, as indicated by a copyright notice that is
included
+in or attached to the work (an example is provided in the Appendix below).
+
+“Derivative Works” shall mean any work, whether in Source or Object form, that
+is based on (or derived from) the Work and for which the editorial revisions,
+annotations, elaborations, or other modifications represent, as a whole, an
+original work of authorship. For the purposes of this License, Derivative Works
+shall not include works that remain separable from, or merely link (or bind by
+name) to the interfaces of, the Work and Derivative Works thereof.
+
+“Contribution” shall mean any work of authorship, including the original
version
+of the Work and any modifications or additions to that Work or Derivative Works
+thereof, that is intentionally submitted to Licensor for inclusion in the Work
+by the copyright owner or by an individual or Legal Entity authorized to submit
+on behalf of the copyright owner. For the purposes of this definition,
+“submitted” means any form of electronic, verbal, or written communication sent
+to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems, and
+issue tracking systems that are managed by, or on behalf of, the Licensor for
+the purpose of discussing and improving the Work, but excluding communication
+that is conspicuously marked or otherwise designated in writing by the
copyright
+owner as “Not a Contribution.”
+
+“Contributor” shall mean Licensor and any individual or Legal Entity on behalf
+of whom a Contribution has been received by Licensor and subsequently
+incorporated within the Work.
+
+#### 2. Grant of Copyright License
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the Work and
such
+Derivative Works in Source or Object form.
+
+#### 3. Grant of Patent License
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable (except as stated in this section) patent license to make, have
+made, use, offer to sell, sell, import, and otherwise transfer the Work, where
+such license applies only to those patent claims licensable by such Contributor
+that are necessarily infringed by their Contribution(s) alone or by combination
+of their Contribution(s) with the Work to which such Contribution(s) was
+submitted. If You institute patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Work or a
+Contribution incorporated within the Work constitutes direct or contributory
+patent infringement, then any patent licenses granted to You under this License
+for that Work shall terminate as of the date such litigation is filed.
+
+#### 4. Redistribution
+
+You may reproduce and distribute copies of the Work or Derivative Works thereof
+in any medium, with or without modifications, and in Source or Object form,
+provided that You meet the following conditions:
+
+* **(a)** You must give any other recipients of the Work or Derivative Works a
copy of
+this License; and
+* **(b)** You must cause any modified files to carry prominent notices stating
that You
+changed the files; and
+* **(c)** You must retain, in the Source form of any Derivative Works that You
distribute,
+all copyright, patent, trademark, and attribution notices from the Source form
+of the Work, excluding those notices that do not pertain to any part of the
+Derivative Works; and
+* **(d)** If the Work includes a “NOTICE” text file as part of its
distribution, then any
+Derivative Works that You distribute must include a readable copy of the
+attribution notices contained within such NOTICE file, excluding those notices
+that do not pertain to any part of the Derivative Works, in at least one of the
+following places: within a NOTICE text file distributed as part of the
+Derivative Works; within the Source form or documentation, if provided along
+with the Derivative Works; or, within a display generated by the Derivative
+Works, if and wherever such third-party notices normally appear. The contents
of
+the NOTICE file are for informational purposes only and do not modify the
+License. You may add Your own attribution notices within Derivative Works that
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
+provided that such additional attribution notices cannot be construed as
+modifying the License.
+
+You may add Your own copyright statement to Your modifications and may provide
+additional or different license terms and conditions for use, reproduction, or
+distribution of Your modifications, or for any such Derivative Works as a
whole,
+provided Your use, reproduction, and distribution of the Work otherwise
complies
+with the conditions stated in this License.
+
+#### 5. Submission of Contributions
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted
+for inclusion in the Work by You to the Licensor shall be under the terms and
+conditions of this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify the terms
of
+any separate license agreement you may have executed with Licensor regarding
+such Contributions.
+
+#### 6. Trademarks
+
+This License does not grant permission to use the trade names, trademarks,
+service marks, or product names of the Licensor, except as required for
+reasonable and customary use in describing the origin of the Work and
+reproducing the content of the NOTICE file.
+
+#### 7. Disclaimer of Warranty
+
+Unless required by applicable law or agreed to in writing, Licensor provides
the
+Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
+including, without limitation, any warranties or conditions of TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
+solely responsible for determining the appropriateness of using or
+redistributing the Work and assume any risks associated with Your exercise of
+permissions under this License.
+
+#### 8. Limitation of Liability
+
+In no event and under no legal theory, whether in tort (including negligence),
+contract, or otherwise, unless required by applicable law (such as deliberate
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special, incidental,
+or consequential damages of any character arising as a result of this License
or
+out of the use or inability to use the Work (including but not limited to
+damages for loss of goodwill, work stoppage, computer failure or malfunction,
or
+any and all other commercial damages or losses), even if such Contributor has
+been advised of the possibility of such damages.
+
+#### 9. Accepting Warranty or Additional Liability
+
+While redistributing the Work or Derivative Works thereof, You may choose to
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
+other liability obligations and/or rights consistent with this License.
However,
+in accepting such obligations, You may act only on Your own behalf and on Your
+sole responsibility, not on behalf of any other Contributor, and only if You
+agree to indemnify, defend, and hold each Contributor harmless for any
liability
+incurred by, or claims asserted against, such Contributor by reason of your
+accepting any such warranty or additional liability.
+
+_END OF TERMS AND CONDITIONS_
+
+### APPENDIX: How to apply the Apache License to your work
+
+To apply the Apache License to your work, attach the following boilerplate
+notice, with the fields enclosed by brackets `[]` replaced with your own
+identifying information. (Don't include the brackets!) The text should be
+enclosed in the appropriate comment syntax for the file format. We also
+recommend that a file or class name and description of purpose be included on
+the same “printed page” as the copyright notice for easier identification
within
+third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/r/sedonadb/NAMESPACE b/r/sedonadb/NAMESPACE
new file mode 100644
index 0000000..0be588e
--- /dev/null
+++ b/r/sedonadb/NAMESPACE
@@ -0,0 +1,33 @@
+# Generated by roxygen2: do not edit by hand
+
+S3method("$<-",savvy_sedonadb__sealed)
+S3method("[[<-",savvy_sedonadb__sealed)
+S3method(as.data.frame,sedonadb_dataframe)
+S3method(as_nanoarrow_array_stream,sedonadb_dataframe)
+S3method(as_sedonadb_dataframe,data.frame)
+S3method(as_sedonadb_dataframe,nanoarrow_array)
+S3method(as_sedonadb_dataframe,nanoarrow_array_stream)
+S3method(as_sedonadb_dataframe,sedonadb_dataframe)
+S3method(as_sedonadb_dataframe,sf)
+S3method(dim,sedonadb_dataframe)
+S3method(dimnames,sedonadb_dataframe)
+S3method(head,sedonadb_dataframe)
+S3method(infer_nanoarrow_schema,sedonadb_dataframe)
+S3method(print,InternalContext__bundle)
+S3method(print,InternalDataFrame__bundle)
+S3method(print,sedonadb_dataframe)
+export(as_sedonadb_dataframe)
+export(sd_collect)
+export(sd_compute)
+export(sd_count)
+export(sd_drop_view)
+export(sd_preview)
+export(sd_read_parquet)
+export(sd_sql)
+export(sd_to_view)
+export(sd_view)
+export(sedonadb_adbc)
+importFrom(nanoarrow,as_nanoarrow_array_stream)
+importFrom(nanoarrow,infer_nanoarrow_schema)
+importFrom(utils,head)
+useDynLib(sedonadb, .registration = TRUE)
diff --git a/r/sedonadb/R/000-wrappers.R b/r/sedonadb/R/000-wrappers.R
new file mode 100644
index 0000000..a375d29
--- /dev/null
+++ b/r/sedonadb/R/000-wrappers.R
@@ -0,0 +1,219 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Generated by savvy: do not edit by hand
+#
+# Note:
+# This wrapper file is named as `000-wrappers.R` so that this file is loaded
+# first, which allows users to override the functions defined here (e.g., a
+# print() method for an enum).
+
+#' @useDynLib sedonadb, .registration = TRUE
+#' @keywords internal
+NULL
+
+# Check class and extract the external pointer embedded in the environment
+.savvy_extract_ptr <- function(e, class) {
+ if(is.null(e)) {
+ return(NULL)
+ }
+
+ if(inherits(e, class)) {
+ e$.ptr
+ } else {
+ msg <- paste0("Expected ", class, ", got ", class(e)[1])
+ stop(msg, call. = FALSE)
+ }
+}
+
+# Prohibit modifying environments
+
+#' @export
+`$<-.savvy_sedonadb__sealed` <- function(x, name, value) {
+ class <- gsub("__bundle$", "", class(x)[1])
+ stop(class, " cannot be modified", call. = FALSE)
+}
+
+#' @export
+`[[<-.savvy_sedonadb__sealed` <- function(x, i, value) {
+ class <- gsub("__bundle$", "", class(x)[1])
+ stop(class, " cannot be modified", call. = FALSE)
+}
+
+
+`init_r_runtime_interrupts` <- function(`interrupts_call`, `pkg_env`) {
+ invisible(.Call(savvy_init_r_runtime_interrupts__impl, `interrupts_call`,
`pkg_env`))
+}
+
+
+`sedonadb_adbc_init_func` <- function() {
+ .Call(savvy_sedonadb_adbc_init_func__impl)
+}
+
+### wrapper functions for InternalContext
+
+`InternalContext_data_frame_from_array_stream` <- function(self) {
+ function(`stream_xptr`, `collect_now`) {
+
.savvy_wrap_InternalDataFrame(.Call(savvy_InternalContext_data_frame_from_array_stream__impl,
`self`, `stream_xptr`, `collect_now`))
+ }
+}
+
+`InternalContext_deregister_table` <- function(self) {
+ function(`table_ref`) {
+ invisible(.Call(savvy_InternalContext_deregister_table__impl, `self`,
`table_ref`))
+ }
+}
+
+`InternalContext_read_parquet` <- function(self) {
+ function(`paths`) {
+
.savvy_wrap_InternalDataFrame(.Call(savvy_InternalContext_read_parquet__impl,
`self`, `paths`))
+ }
+}
+
+`InternalContext_sql` <- function(self) {
+ function(`query`) {
+ .savvy_wrap_InternalDataFrame(.Call(savvy_InternalContext_sql__impl,
`self`, `query`))
+ }
+}
+
+`InternalContext_view` <- function(self) {
+ function(`table_ref`) {
+ .savvy_wrap_InternalDataFrame(.Call(savvy_InternalContext_view__impl,
`self`, `table_ref`))
+ }
+}
+
+`.savvy_wrap_InternalContext` <- function(ptr) {
+ e <- new.env(parent = emptyenv())
+ e$.ptr <- ptr
+ e$`data_frame_from_array_stream` <-
`InternalContext_data_frame_from_array_stream`(ptr)
+ e$`deregister_table` <- `InternalContext_deregister_table`(ptr)
+ e$`read_parquet` <- `InternalContext_read_parquet`(ptr)
+ e$`sql` <- `InternalContext_sql`(ptr)
+ e$`view` <- `InternalContext_view`(ptr)
+
+ class(e) <- c("InternalContext", "savvy_sedonadb__sealed")
+ e
+}
+
+
+
+`InternalContext` <- new.env(parent = emptyenv())
+
+### associated functions for InternalContext
+
+`InternalContext`$`new` <- function() {
+ .savvy_wrap_InternalContext(.Call(savvy_InternalContext_new__impl))
+}
+
+
+class(`InternalContext`) <- c("InternalContext__bundle",
"savvy_sedonadb__sealed")
+
+#' @export
+`print.InternalContext__bundle` <- function(x, ...) {
+ cat('InternalContext\n')
+}
+
+### wrapper functions for InternalDataFrame
+
+`InternalDataFrame_collect` <- function(self) {
+ function(`out`) {
+ .Call(savvy_InternalDataFrame_collect__impl, `self`, `out`)
+ }
+}
+
+`InternalDataFrame_compute` <- function(self) {
+ function(`ctx`) {
+ `ctx` <- .savvy_extract_ptr(`ctx`, "InternalContext")
+ .savvy_wrap_InternalDataFrame(.Call(savvy_InternalDataFrame_compute__impl,
`self`, `ctx`))
+ }
+}
+
+`InternalDataFrame_count` <- function(self) {
+ function() {
+ .Call(savvy_InternalDataFrame_count__impl, `self`)
+ }
+}
+
+`InternalDataFrame_limit` <- function(self) {
+ function(`n`) {
+ .savvy_wrap_InternalDataFrame(.Call(savvy_InternalDataFrame_limit__impl,
`self`, `n`))
+ }
+}
+
+`InternalDataFrame_primary_geometry_column_index` <- function(self) {
+ function() {
+ .Call(savvy_InternalDataFrame_primary_geometry_column_index__impl, `self`)
+ }
+}
+
+`InternalDataFrame_show` <- function(self) {
+ function(`ctx`, `width_chars`, `ascii`, `limit` = NULL) {
+ `ctx` <- .savvy_extract_ptr(`ctx`, "InternalContext")
+ .Call(savvy_InternalDataFrame_show__impl, `self`, `ctx`, `width_chars`,
`ascii`, `limit`)
+ }
+}
+
+`InternalDataFrame_to_arrow_schema` <- function(self) {
+ function(`out`) {
+ invisible(.Call(savvy_InternalDataFrame_to_arrow_schema__impl, `self`,
`out`))
+ }
+}
+
+`InternalDataFrame_to_arrow_stream` <- function(self) {
+ function(`out`) {
+ invisible(.Call(savvy_InternalDataFrame_to_arrow_stream__impl, `self`,
`out`))
+ }
+}
+
+`InternalDataFrame_to_view` <- function(self) {
+ function(`ctx`, `table_ref`, `overwrite`) {
+ `ctx` <- .savvy_extract_ptr(`ctx`, "InternalContext")
+ invisible(.Call(savvy_InternalDataFrame_to_view__impl, `self`, `ctx`,
`table_ref`, `overwrite`))
+ }
+}
+
+`.savvy_wrap_InternalDataFrame` <- function(ptr) {
+ e <- new.env(parent = emptyenv())
+ e$.ptr <- ptr
+ e$`collect` <- `InternalDataFrame_collect`(ptr)
+ e$`compute` <- `InternalDataFrame_compute`(ptr)
+ e$`count` <- `InternalDataFrame_count`(ptr)
+ e$`limit` <- `InternalDataFrame_limit`(ptr)
+ e$`primary_geometry_column_index` <-
`InternalDataFrame_primary_geometry_column_index`(ptr)
+ e$`show` <- `InternalDataFrame_show`(ptr)
+ e$`to_arrow_schema` <- `InternalDataFrame_to_arrow_schema`(ptr)
+ e$`to_arrow_stream` <- `InternalDataFrame_to_arrow_stream`(ptr)
+ e$`to_view` <- `InternalDataFrame_to_view`(ptr)
+
+ class(e) <- c("InternalDataFrame", "savvy_sedonadb__sealed")
+ e
+}
+
+
+
+`InternalDataFrame` <- new.env(parent = emptyenv())
+
+### associated functions for InternalDataFrame
+
+
+
+class(`InternalDataFrame`) <- c("InternalDataFrame__bundle",
"savvy_sedonadb__sealed")
+
+#' @export
+`print.InternalDataFrame__bundle` <- function(x, ...) {
+ cat('InternalDataFrame\n')
+}
diff --git a/.gitignore b/r/sedonadb/R/adbc.R
similarity index 58%
copy from .gitignore
copy to r/sedonadb/R/adbc.R
index 7441737..6f2157b 100644
--- a/.gitignore
+++ b/r/sedonadb/R/adbc.R
@@ -15,31 +15,23 @@
# specific language governing permissions and limitations
# under the License.
-# MacOS
-.DS_Store
-
-# sedona-cli
-.history
-
-# Generated by cargo
-debug/
-target/
-
-# Generated by rustfmt
-**/*.rs.bk
-
-# Generated by cargo llvm-cov
-coverage/
-
-# MSVC Windows builds of rustc generate these, which store debugging
information
-*.pdb
-
-# IDE-specific settings
-.idea/
-.vscode/
-
-# documentation
-site/
-
-# Python cache files
-__pycache__
+#' SedonaDB ADBC Driver
+#'
+#' @returns An [adbcdrivermanager::adbc_driver()] of class
+#' 'sedonadb_driver_sedonadb'
+#' @export
+#'
+#' @examples
+#' library(adbcdrivermanager)
+#'
+#' con <- sedonadb_adbc() |>
+#' adbc_database_init() |>
+#' adbc_connection_init()
+#' con |>
+#' read_adbc("SELECT ST_Point(0, 1) as geometry") |>
+#' as.data.frame()
+#'
+sedonadb_adbc <- function() {
+ init_func <- structure(sedonadb_adbc_init_func(), class =
"adbc_driver_init_func")
+ adbcdrivermanager::adbc_driver(init_func, subclass =
"sedonadb_driver_sedonadb")
+}
diff --git a/r/sedonadb/R/context.R b/r/sedonadb/R/context.R
new file mode 100644
index 0000000..2b6342b
--- /dev/null
+++ b/r/sedonadb/R/context.R
@@ -0,0 +1,95 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+#' Create a DataFrame from one or more Parquet files
+#'
+#' The query will only be executed when requested.
+#'
+#' @param path One or more paths or URIs to Parquet files
+#'
+#' @returns A sedonadb_dataframe
+#' @export
+#'
+#' @examples
+#' path <- system.file("files/natural-earth_cities_geo.parquet", package =
"sedonadb")
+#' sd_read_parquet(path) |> head(5) |> sd_preview()
+#'
+sd_read_parquet <- function(path) {
+ ctx <- ctx()
+ df <- ctx$read_parquet(path)
+ new_sedonadb_dataframe(ctx, df)
+}
+
+#' Create a DataFrame from SQL
+#'
+#' The query will only be executed when requested.
+#'
+#' @param sql A SQL string to execute
+#'
+#' @returns A sedonadb_dataframe
+#' @export
+#'
+#' @examples
+#' sd_sql("SELECT ST_Point(0, 1) as geom") |> sd_preview()
+#'
+sd_sql <- function(sql) {
+ ctx <- ctx()
+ df <- ctx$sql(sql)
+ new_sedonadb_dataframe(ctx, df)
+}
+
+#' Create or Drop a named view
+#'
+#' Remove a view created with [sd_to_view()] from the context.
+#'
+#' @param table_ref The name of the view reference
+#' @returns The context, invisibly
+#' @export
+#'
+#' @examples
+#' sd_sql("SELECT 1 as one") |> sd_to_view("foofy")
+#' sd_view("foofy")
+#' sd_drop_view("foofy")
+#' try(sd_view("foofy"))
+#'
+sd_drop_view <- function(table_ref) {
+ ctx <- ctx()
+ ctx$deregister_table(table_ref)
+ invisible(ctx)
+}
+
+#' @rdname sd_drop_view
+#' @export
+sd_view <- function(table_ref) {
+ ctx <- ctx()
+ df <- ctx$view(table_ref)
+ new_sedonadb_dataframe(ctx, df)
+}
+
+# We use just one context for now. In theory we could support multiple
+# contexts with a shared runtime, which would scope the registration
+# of various components more cleanly from the runtime.
+ctx <- function() {
+ if (is.null(global_ctx$ctx)) {
+ global_ctx$ctx <- InternalContext$new()
+ }
+
+ global_ctx$ctx
+}
+
+global_ctx <- new.env(parent = emptyenv())
+global_ctx$ctx <- NULL
diff --git a/r/sedonadb/R/dataframe.R b/r/sedonadb/R/dataframe.R
new file mode 100644
index 0000000..321961c
--- /dev/null
+++ b/r/sedonadb/R/dataframe.R
@@ -0,0 +1,249 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+#' Convert an object to a DataFrame
+#'
+#' @param x An object to convert
+#' @param ... Extra arguments passed to/from methods
+#' @param schema The requested schema
+#'
+#' @returns A sedonadb_dataframe
+#' @export
+#'
+#' @examples
+#' as_sedonadb_dataframe(data.frame(x = 1:3))
+#'
+as_sedonadb_dataframe <- function(x, ..., schema = NULL) {
+ UseMethod("as_sedonadb_dataframe")
+}
+
+#' @export
+as_sedonadb_dataframe.sedonadb_dataframe <- function(x, ..., schema = NULL) {
+ # In the future, schema can be handled with a cast
+ x
+}
+
+#' @export
+as_sedonadb_dataframe.data.frame <- function(x, ..., schema = NULL) {
+ array <- nanoarrow::as_nanoarrow_array(x, schema = schema)
+ stream <- nanoarrow::basic_array_stream(list(array))
+ ctx <- ctx()
+ df <- ctx$data_frame_from_array_stream(stream, collect_now = TRUE)
+ new_sedonadb_dataframe(ctx, df)
+}
+
+#' @export
+as_sedonadb_dataframe.nanoarrow_array <- function(x, ..., schema = NULL) {
+ stream <- nanoarrow::as_nanoarrow_array_stream(x, schema = schema)
+ ctx <- ctx()
+ df <- ctx$data_frame_from_array_stream(stream, collect_now = TRUE)
+
+ # Verify schema is handled
+ as_sedonadb_dataframe(new_sedonadb_dataframe(ctx, df), schema = schema)
+}
+
+#' @export
+as_sedonadb_dataframe.nanoarrow_array_stream <- function(x, ..., schema = NULL,
+ lazy = TRUE) {
+ stream <- nanoarrow::as_nanoarrow_array_stream(x, schema = schema)
+ ctx <- ctx()
+ df <- ctx$data_frame_from_array_stream(stream, collect_now = !lazy)
+
+ # Verify schema is handled
+ as_sedonadb_dataframe(new_sedonadb_dataframe(ctx, df), schema = schema)
+}
+
+#' Count rows in a DataFrame
+#'
+#' @param .data A sedonadb_dataframe
+#'
+#' @returns The number of rows after executing the query
+#' @export
+#'
+#' @examples
+#' sd_sql("SELECT 1 as one") |> sd_count()
+#'
+sd_count <- function(.data) {
+ .data$df$count()
+}
+
+#' Register a DataFrame as a named view
+#'
+#' This is useful for creating a view that can be referenced in a SQL
+#' statement. Use [sd_drop_view()] to remove it.
+#'
+#' @inheritParams sd_count
+#' @inheritParams sd_drop_view
+#' @param overwrite Use TRUE to overwrite a view with the same name (if it
exists)
+#'
+#' @returns .data, invisibly
+#' @export
+#'
+#' @examples
+#' sd_sql("SELECT 1 as one") |> sd_to_view("foofy")
+#' sd_sql("SELECT * FROM foofy")
+#'
+sd_to_view <- function(.data, table_ref, overwrite = FALSE) {
+ .data <- as_sedonadb_dataframe(.data)
+ .data$df$to_view(.data$ctx, table_ref, overwrite)
+ invisible(.data)
+}
+
+#' Collect a DataFrame into memory
+#'
+#' Use `sd_compute()` to collect and return the result as a DataFrame;
+#' use `sd_collect()` to collect and return the result as an R data.frame.
+#'
+#' @inheritParams sd_count
+#' @param ptype The target R object. See [nanoarrow::convert_array_stream].
+#'
+#' @returns `sd_compute()` returns a sedonadb_dataframe; `sd_collect()` returns
+#' a data.frame (or subclass according to `ptype`).
+#' @export
+#'
+#' @examples
+#' sd_sql("SELECT 1 as one") |> sd_compute()
+#' sd_sql("SELECT 1 as one") |> sd_collect()
+#'
+sd_compute <- function(.data) {
+ .data <- as_sedonadb_dataframe(.data)
+ df <- .data$df$compute(.data$ctx)
+ new_sedonadb_dataframe(.data$ctx, df)
+}
+
+#' @export
+#' @rdname sd_compute
+sd_collect <- function(.data, ptype = NULL) {
+ .data <- as_sedonadb_dataframe(.data)
+ stream <- nanoarrow::nanoarrow_allocate_array_stream()
+ size <- .data$df$collect(stream)
+ nanoarrow::convert_array_stream(stream, size = size, to = ptype)
+}
+
+#' Preview and print the results of running a query
+#'
+#' This is used to implement `print()` for the sedonadb_dataframe or can
+#' be used to explicitly preview if `options(sedonadb.interactive = FALSE)`.
+#'
+#' @inheritParams sd_count
+#' @param n The number of rows to preview. Use `Inf` to preview all rows.
+#' Defaults to `getOption("pillar.print_max")`.
+#' @param ascii Use `TRUE` to force ASCII table formatting or `FALSE` to force
+#' unicode formatting. By default, use a heuristic to determine if the output
+#' is unicode-friendly or the value of `getOption("cli.unicode")`.
+#' @param width The character width of the output. Defaults to
+#' `getOption("width")`.
+#'
+#' @returns .data, invisibly
+#' @export
+#'
+#' @examples
+#' sd_sql("SELECT 1 as one") |> sd_preview()
+#'
+sd_preview <- function(.data, n = NULL, ascii = NULL, width = NULL) {
+ .data <- as_sedonadb_dataframe(.data)
+
+ if (is.null(width)) {
+ width <- getOption("width")
+ }
+
+ if (is.null(n)) {
+ n <- getOption("pillar.print_max", 6)
+ }
+
+ if (is.null(ascii)) {
+ ascii <- !is_utf8_output()
+ }
+
+ content <- .data$df$show(
+ .data$ctx,
+ width_chars = as.integer(width),
+ limit = as.double(n),
+ ascii = ascii
+ )
+
+ cat(content)
+ cat(paste0("Preview of up to ", n, " row(s)\n"))
+
+ invisible(.data)
+}
+
+
+new_sedonadb_dataframe <- function(ctx, internal_df) {
+ structure(list(ctx = ctx, df = internal_df), class = "sedonadb_dataframe")
+}
+
+#' @importFrom utils head
+#' @export
+head.sedonadb_dataframe <- function(x, n = 6L, ...) {
+ new_sedonadb_dataframe(x$ctx, x$df$limit(as.double(n)))
+}
+
+#' @export
+dimnames.sedonadb_dataframe <- function(x, ...) {
+ list(NULL, names(infer_nanoarrow_schema(x)$children))
+}
+
+#' @export
+dim.sedonadb_dataframe <- function(x, ...) {
+ c(NA_integer_, length(infer_nanoarrow_schema(x)$children))
+}
+
+#' @export
+as.data.frame.sedonadb_dataframe <- function(x, ...) {
+ stream <- nanoarrow::nanoarrow_allocate_array_stream()
+ size <- x$df$collect(stream)
+ nanoarrow::convert_array_stream(stream, size = size)
+}
+
+#' @importFrom nanoarrow infer_nanoarrow_schema
+#' @export
+infer_nanoarrow_schema.sedonadb_dataframe <- function(x, ...) {
+ schema <- nanoarrow::nanoarrow_allocate_schema()
+ x$df$to_arrow_schema(schema)
+ schema
+}
+
+#' @importFrom nanoarrow as_nanoarrow_array_stream
+#' @export
+as_nanoarrow_array_stream.sedonadb_dataframe <- function(x, ...) {
+ stream <- nanoarrow::nanoarrow_allocate_array_stream()
+ x$df$to_arrow_stream(stream)
+ stream
+}
+
+#' @export
+print.sedonadb_dataframe <- function(x, ..., width = NULL, n = NULL) {
+ if (isTRUE(getOption("sedonadb.interactive", TRUE))) {
+ sd_preview(x, n = n, width = width)
+ } else {
+ sd_preview(x, n = 0)
+ cat("Use options(sedonadb.interactive = TRUE) or use sd_preview() to
print\n")
+ }
+
+ invisible(x)
+}
+
+# Borrowed from cli but without detecting LaTeX output.
+is_utf8_output <- function() {
+ opt <- getOption("cli.unicode", NULL)
+ if (!is.null(opt)) {
+ isTRUE(opt)
+ } else {
+ l10n_info()$`UTF-8`
+ }
+}
diff --git a/.gitignore b/r/sedonadb/R/pkg-sf.R
similarity index 57%
copy from .gitignore
copy to r/sedonadb/R/pkg-sf.R
index 7441737..d402457 100644
--- a/.gitignore
+++ b/r/sedonadb/R/pkg-sf.R
@@ -15,31 +15,23 @@
# specific language governing permissions and limitations
# under the License.
-# MacOS
-.DS_Store
-
-# sedona-cli
-.history
-
-# Generated by cargo
-debug/
-target/
-
-# Generated by rustfmt
-**/*.rs.bk
-
-# Generated by cargo llvm-cov
-coverage/
-
-# MSVC Windows builds of rustc generate these, which store debugging
information
-*.pdb
-
-# IDE-specific settings
-.idea/
-.vscode/
-
-# documentation
-site/
-
-# Python cache files
-__pycache__
+#' @export
+as_sedonadb_dataframe.sf <- function(x, ..., schema = NULL) {
+ stream <- nanoarrow::as_nanoarrow_array_stream(
+ x,
+ schema = schema,
+ geometry_schema = geoarrow::geoarrow_wkb()
+ )
+ ctx <- ctx()
+ df <- ctx$data_frame_from_array_stream(stream, collect_now = TRUE)
+
+ # Verify schema is handled
+ as_sedonadb_dataframe(new_sedonadb_dataframe(ctx, df), schema = schema)
+}
+
+# dynamically registered in zzz.R
+st_as_sf.sedonadb_dataframe <- function(x, ...) {
+ stream <- nanoarrow::nanoarrow_allocate_array_stream()
+ size <- x$df$collect(stream)
+ sf::st_as_sf(stream)
+}
diff --git a/.gitignore b/r/sedonadb/R/sedonadb-package.R
similarity index 69%
copy from .gitignore
copy to r/sedonadb/R/sedonadb-package.R
index 7441737..d8b6f3b 100644
--- a/.gitignore
+++ b/r/sedonadb/R/sedonadb-package.R
@@ -14,32 +14,9 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
+#' @keywords internal
+"_PACKAGE"
-# MacOS
-.DS_Store
-
-# sedona-cli
-.history
-
-# Generated by cargo
-debug/
-target/
-
-# Generated by rustfmt
-**/*.rs.bk
-
-# Generated by cargo llvm-cov
-coverage/
-
-# MSVC Windows builds of rustc generate these, which store debugging
information
-*.pdb
-
-# IDE-specific settings
-.idea/
-.vscode/
-
-# documentation
-site/
-
-# Python cache files
-__pycache__
+## usethis namespace: start
+## usethis namespace: end
+NULL
diff --git a/r/sedonadb/R/zzz.R b/r/sedonadb/R/zzz.R
new file mode 100644
index 0000000..f9e6d18
--- /dev/null
+++ b/r/sedonadb/R/zzz.R
@@ -0,0 +1,115 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+.onLoad <- function(...) {
+ # Load geoarrow to manage conversion of arrow results to/from spatial objects
+ requireNamespace("geoarrow", quietly = TRUE)
+
+ s3_register("sf::st_as_sf", "sedonadb_dataframe")
+
+ # Inject what we need to reduce the Rust code to a simple Rf_eval()
+ ns <- asNamespace("sedonadb")
+ call <- call("check_interrupts")
+ init_r_runtime_interrupts(call, ns)
+}
+
+# The function we call from Rust to check for interrupts. R checks for
+# interrupts automatically when evaluating regular R code and signals
+# an interrupt condition,
+check_interrupts <- function() {
+ tryCatch({
+ FALSE
+ }, interrupt = function(...) TRUE, error = function(...) TRUE)
+}
+
+# Permissively licensed (unlicense) from
+#
https://github.com/r-lib/vctrs/blob/b63a233bd8b8d6511cf3f7d9182c359a3b8c3cab/R/register-s3.R
+s3_register <- function(generic, class, method = NULL) {
+ stopifnot(is.character(generic), length(generic) == 1)
+ stopifnot(is.character(class), length(class) == 1)
+
+ pieces <- strsplit(generic, "::")[[1]]
+ stopifnot(length(pieces) == 2)
+ package <- pieces[[1]]
+ generic <- pieces[[2]]
+
+ caller <- parent.frame()
+
+ get_method_env <- function() {
+ top <- topenv(caller)
+ if (isNamespace(top)) {
+ asNamespace(environmentName(top))
+ } else {
+ caller
+ }
+ }
+ get_method <- function(method) {
+ if (is.null(method)) {
+ get(paste0(generic, ".", class), envir = get_method_env())
+ } else {
+ method
+ }
+ }
+
+ register <- function(...) {
+ envir <- asNamespace(package)
+
+ # Refresh the method each time, it might have been updated by
+ # `devtools::load_all()`
+ method_fn <- get_method(method)
+ stopifnot(is.function(method_fn))
+
+
+ # Only register if generic can be accessed
+ if (exists(generic, envir)) {
+ registerS3method(generic, class, method_fn, envir = envir)
+ } else if (identical(Sys.getenv("NOT_CRAN"), "true")) {
+ warn <- .rlang_s3_register_compat("warn")
+
+ warn(c(
+ sprintf(
+ "Can't find generic `%s` in package %s to register S3 method.",
+ generic,
+ package
+ ),
+ "i" = "This message is only shown to developers using devtools.",
+ "i" = sprintf("Do you need to update %s to the latest version?",
package)
+ ))
+ }
+ }
+
+ # Always register hook in case package is later unloaded & reloaded
+ setHook(packageEvent(package, "onLoad"), function(...) {
+ register()
+ })
+
+ # For compatibility with R < 4.0 where base isn't locked
+ is_sealed <- function(pkg) {
+ identical(pkg, "base") || environmentIsLocked(asNamespace(pkg))
+ }
+
+ # Avoid registration failures during loading (pkgload or regular).
+ # Check that environment is locked because the registering package
+ # might be a dependency of the package that exports the generic. In
+ # that case, the exports (and the generic) might not be populated
+ # yet (#1225).
+ if (isNamespaceLoaded(package) && is_sealed(package)) {
+ register()
+ }
+
+ invisible()
+}
diff --git a/r/sedonadb/README.Rmd b/r/sedonadb/README.Rmd
new file mode 100644
index 0000000..5b9a784
--- /dev/null
+++ b/r/sedonadb/README.Rmd
@@ -0,0 +1,88 @@
+---
+output: github_document
+---
+
+<!---
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+
+```{r, include = FALSE}
+knitr::opts_chunk$set(
+ collapse = TRUE,
+ comment = "#>",
+ fig.path = "man/figures/README-",
+ out.width = "100%",
+ dpi = 300
+)
+```
+
+# sedonadb
+
+<!-- badges: start -->
+<!-- badges: end -->
+
+The goal of sedonadb is to provide an R interface to [Apache
SedonaDB](https://sedona.apache.org/sedonadb). SedonaDB provides a
[DataFusion](https://datafusion.apache.org)-powered single-node engine with a
wide range of spatial capabilities built in, including spatial SQL with high
function coverage and GeoParquet IO.
+
+## Installation
+
+You can install the development version of sedonadb from
[GitHub](https://github.com/) with:
+
+``` shell
+git clone https://github.com/apache/sedona-db.git
+cd sedona-db/r/sedonadb
+R CMD INSTALL .
+```
+
+Installing a development version of sedonadb requires a [Rust
compiler](https://rustup.rs) and a GEOS system dependency (e.g., `brew install
geos` or `apt-get install libgeos-dev`). Install instructions for these
dependencies on other platforms can be found on the [sf package
homepage](https://r-spatial.github.io/sf).
+
+## Example
+
+You can use SedonaDB to read (Geo)Parquet files from anywhere. Filters are used
+to reduce the amount of data downloaded based on GeoParquet and/or Parquet
+metadata:
+
+```{r example}
+library(sedonadb)
+
+url <-
"https://github.com/geoarrow/geoarrow-data/releases/download/v0.2.0/microsoft-buildings_point_geo.parquet"
+sd_read_parquet(url) |> sd_to_view("buildings", overwrite = TRUE)
+
+filter <- "POLYGON ((-73.4341 44.0087, -73.4341 43.7981, -73.2531 43.7981,
-73.2531 43.8889, -73.1531 43.8889, -73.1531 44.0087, -73.4341 44.0087))"
+
+sd_sql(glue::glue("
+ SELECT * FROM buildings
+ WHERE ST_Intersects(ST_SetSRID(ST_GeomFromText('{filter}'), 4326), geometry)
+")) |> sd_preview()
+```
+
+Conversion to and from sf are supported:
+
+```{r}
+library(sf)
+
+nc <- sf::read_sf(system.file("shape/nc.shp", package = "sf"))
+nc |> sd_to_view("nc", overwrite = TRUE)
+
+sd_sql("SELECT * FROM nc")
+```
+
+```{r}
+sd_sql("SELECT ST_Buffer(geometry, 0.1) as geometry FROM nc") |>
+ st_as_sf() |>
+ plot()
+```
diff --git a/r/sedonadb/README.md b/r/sedonadb/README.md
new file mode 100644
index 0000000..20bc812
--- /dev/null
+++ b/r/sedonadb/README.md
@@ -0,0 +1,120 @@
+
+<!---
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+
+# sedonadb
+
+<!-- badges: start -->
+
+<!-- badges: end -->
+
+The goal of sedonadb is to provide an R interface to [Apache
+SedonaDB](https://sedona.apache.org/sedonadb). SedonaDB provides a
+[DataFusion](https://datafusion.apache.org)-powered single-node engine
+with a wide range of spatial capabilities built in, including spatial
+SQL with high function coverage and GeoParquet IO.
+
+## Installation
+
+You can install the development version of sedonadb from
+[GitHub](https://github.com/) with:
+
+``` shell
+git clone https://github.com/apache/sedona-db.git
+cd sedona-db/r/sedonadb
+R CMD INSTALL .
+```
+
+Installing a development version of sedonadb requires a [Rust
+compiler](https://rustup.rs) and a GEOS system dependency (e.g.,
+`brew install geos` or `apt-get install libgeos-dev`). Install
+instructions for these dependencies on other platforms can be found on
+the [sf package homepage](https://r-spatial.github.io/sf).
+
+## Example
+
+You can use SedonaDB to read (Geo)Parquet files from anywhere. Filters
+are used to reduce the amount of data downloaded based on GeoParquet
+and/or Parquet metadata:
+
+``` r
+library(sedonadb)
+
+url <-
"https://github.com/geoarrow/geoarrow-data/releases/download/v0.2.0/microsoft-buildings_point_geo.parquet"
+sd_read_parquet(url) |> sd_to_view("buildings", overwrite = TRUE)
+
+filter <- "POLYGON ((-73.4341 44.0087, -73.4341 43.7981, -73.2531 43.7981,
-73.2531 43.8889, -73.1531 43.8889, -73.1531 44.0087, -73.4341 44.0087))"
+
+sd_sql(glue::glue("
+ SELECT * FROM buildings
+ WHERE ST_Intersects(ST_SetSRID(ST_GeomFromText('{filter}'), 4326), geometry)
+")) |> sd_preview()
+#> ┌─────────────────────────────────┐
+#> │ geometry │
+#> │ geometry │
+#> ╞═════════════════════════════════╡
+#> │ POINT(-73.29533522 44.00847556) │
+#> ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
+#> │ POINT(-73.29092778 44.00421331) │
+#> ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
+#> │ POINT(-73.277808 43.998823) │
+#> ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
+#> │ POINT(-73.277524 44.004619) │
+#> ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
+#> │ POINT(-73.2774573 44.0044719) │
+#> ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
+#> │ POINT(-73.27775838 44.0046742) │
+#> └─────────────────────────────────┘
+#> Preview of up to 6 row(s)
+```
+
+Conversion to and from sf are supported:
+
+``` r
+library(sf)
+#> Linking to GEOS 3.13.0, GDAL 3.8.5, PROJ 9.5.1; sf_use_s2() is TRUE
+
+nc <- sf::read_sf(system.file("shape/nc.shp", package = "sf"))
+nc |> sd_to_view("nc", overwrite = TRUE)
+
+sd_sql("SELECT * FROM nc")
+#>
┌─────────┬───┬─────────┬──────────────────────────────────────────────────────┐
+#> │ AREA ┆ … ┆ NWBIR79 ┆ geometry
│
+#> │ float64 ┆ ┆ float64 ┆ geometry
│
+#>
╞═════════╪═══╪═════════╪══════════════════════════════════════════════════════╡
+#> │ 0.114 ┆ … ┆ 19.0 ┆ MULTIPOLYGON(((-81.4727554321289
36.23435592651367,… │
+#>
├╌╌╌╌╌╌╌╌╌┼╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
+#> │ 0.061 ┆ … ┆ 12.0 ┆ MULTIPOLYGON(((-81.2398910522461
36.36536407470703,… │
+#>
├╌╌╌╌╌╌╌╌╌┼╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
+#> │ 0.143 ┆ … ┆ 260.0 ┆ MULTIPOLYGON(((-80.45634460449219
36.24255752563476… │
+#>
├╌╌╌╌╌╌╌╌╌┼╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
+#> │ 0.07 ┆ … ┆ 145.0 ┆ MULTIPOLYGON(((-76.00897216796875
36.31959533691406… │
+#>
├╌╌╌╌╌╌╌╌╌┼╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
+#> │ 0.153 ┆ … ┆ 1197.0 ┆ MULTIPOLYGON(((-77.21766662597656
36.24098205566406… │
+#>
├╌╌╌╌╌╌╌╌╌┼╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
+#> │ 0.097 ┆ … ┆ 1237.0 ┆ MULTIPOLYGON(((-76.74506378173828
36.23391723632812… │
+#>
└─────────┴───┴─────────┴──────────────────────────────────────────────────────┘
+#> Preview of up to 6 row(s)
+```
+
+``` r
+sd_sql("SELECT ST_Buffer(geometry, 0.1) as geometry FROM nc") |>
+ st_as_sf() |>
+ plot()
+```
+
+<img src="man/figures/README-unnamed-chunk-3-1.png" width="100%" />
diff --git a/.gitignore b/r/sedonadb/cleanup
old mode 100644
new mode 100755
similarity index 69%
copy from .gitignore
copy to r/sedonadb/cleanup
index 7441737..00c8865
--- a/.gitignore
+++ b/r/sedonadb/cleanup
@@ -15,31 +15,4 @@
# specific language governing permissions and limitations
# under the License.
-# MacOS
-.DS_Store
-
-# sedona-cli
-.history
-
-# Generated by cargo
-debug/
-target/
-
-# Generated by rustfmt
-**/*.rs.bk
-
-# Generated by cargo llvm-cov
-coverage/
-
-# MSVC Windows builds of rustc generate these, which store debugging
information
-*.pdb
-
-# IDE-specific settings
-.idea/
-.vscode/
-
-# documentation
-site/
-
-# Python cache files
-__pycache__
+rm -f src/Makevars
diff --git a/.gitignore b/r/sedonadb/cleanup.win
old mode 100644
new mode 100755
similarity index 69%
copy from .gitignore
copy to r/sedonadb/cleanup.win
index 7441737..e81e481
--- a/.gitignore
+++ b/r/sedonadb/cleanup.win
@@ -15,31 +15,4 @@
# specific language governing permissions and limitations
# under the License.
-# MacOS
-.DS_Store
-
-# sedona-cli
-.history
-
-# Generated by cargo
-debug/
-target/
-
-# Generated by rustfmt
-**/*.rs.bk
-
-# Generated by cargo llvm-cov
-coverage/
-
-# MSVC Windows builds of rustc generate these, which store debugging
information
-*.pdb
-
-# IDE-specific settings
-.idea/
-.vscode/
-
-# documentation
-site/
-
-# Python cache files
-__pycache__
+rm -f src/Makevars.win
diff --git a/r/sedonadb/configure b/r/sedonadb/configure
new file mode 100755
index 0000000..a4f74a6
--- /dev/null
+++ b/r/sedonadb/configure
@@ -0,0 +1,91 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# We need to link GEOS libs. The cargo build will use pkg-config
+# and then geos-config, in that order, and requires pkg-config to be
+# available (even if it isn't used). Here we just try pkg-config (LIB_DIR
+# can be used to specify the location of -lgeos_c)
+
+pkg-config geos 2>/dev/null
+if [ $? -eq 0 ]; then
+ PKGCONFIG_LIBS=`pkg-config --libs geos`
+fi
+
+if [ "$LIB_DIR" ]; then
+ echo "Found LIB_DIR!"
+ PKG_LIBS="-L$LIB_DIR -lgeos_c $PKG_LIBS"
+elif [ "$PKGCONFIG_CFLAGS" ] || [ "$PKGCONFIG_LIBS" ]; then
+ echo "Found GEOS pkg-config libs!"
+ PKG_LIBS=${PKGCONFIG_LIBS}
+fi
+
+
+# Even when `cargo` is on `PATH`, `rustc` might not in some cases. This adds
+# ~/.cargo/bin to PATH to address such cases. Note that is not always available
+# (e.g. or on Ubuntu with Rust installed via APT).
+if [ -d "${HOME}/.cargo/bin" ]; then
+ export PATH="${PATH}:${HOME}/.cargo/bin"
+fi
+
+CARGO_VERSION="$(cargo --version)"
+
+if [ $? -ne 0 ]; then
+ echo "-------------- ERROR: CONFIGURATION FAILED --------------------"
+ echo ""
+ echo "The cargo command is not available. To install Rust, please refer"
+ echo "to the official instruction:"
+ echo ""
+ echo "https://www.rust-lang.org/tools/install"
+ echo ""
+ echo "---------------------------------------------------------------"
+
+ exit 1
+fi
+
+# There's a little chance that rustc is not available on PATH while cargo is.
+# So, just ignore the error case.
+RUSTC_VERSION="$(rustc --version || true)"
+
+# Report the version of Rustc to comply with the CRAN policy
+echo "using Rust package manager: '${CARGO_VERSION}'"
+echo "using Rust compiler: '${RUSTC_VERSION}'"
+
+if [ "$(uname)" = "Emscripten" ]; then
+ TARGET="wasm32-unknown-emscripten"
+fi
+
+# allow overriding profile externally (e.g. on CI)
+if [ -n "${SAVVY_PROFILE}" ]; then
+ PROFILE="${SAVVY_PROFILE}"
+# catch DEBUG envvar, which is passed from pkgbuild::compile_dll()
+elif [ "${DEBUG}" = "true" ]; then
+ PROFILE=dev
+else
+ PROFILE=release
+fi
+
+# e.g. SAVVY_FEATURES="a b" --> "--features 'a b'"
+if [ -n "${SAVVY_FEATURES}" ]; then
+ FEATURE_FLAGS="--features '${SAVVY_FEATURES}'"
+fi
+
+sed \
+ -e "s/@TARGET@/${TARGET}/" \
+ -e "s/@PROFILE@/${PROFILE}/" \
+ -e "s/@FEATURE_FLAGS@/${FEATURE_FLAGS}/" \
+ -e "s|@PKG_LIBS@|${PKG_LIBS}|" \
+ src/Makevars.in > src/Makevars
diff --git a/r/sedonadb/configure.win b/r/sedonadb/configure.win
new file mode 100755
index 0000000..6db580b
--- /dev/null
+++ b/r/sedonadb/configure.win
@@ -0,0 +1,60 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+CARGO_VERSION="$(cargo --version)"
+
+if [ $? -ne 0 ]; then
+ echo "-------------- ERROR: CONFIGURATION FAILED --------------------"
+ echo ""
+ echo "The cargo command is not available. To install Rust, please refer"
+ echo "to the official instruction:"
+ echo ""
+ echo "https://www.rust-lang.org/tools/install"
+ echo ""
+ echo "---------------------------------------------------------------"
+
+ exit 1
+fi
+
+# There's a little chance that rustc is not available on PATH while cargo is.
+# So, just ignore the error case.
+RUSTC_VERSION="$(rustc --version || true)"
+
+# Report the version of Rustc to comply with the CRAN policy
+echo "using Rust package manager: '${CARGO_VERSION}'"
+echo "using Rust compiler: '${RUSTC_VERSION}'"
+
+# allow overriding profile externally (e.g. on CI)
+if [ -n "${SAVVY_PROFILE}" ]; then
+ PROFILE="${SAVVY_PROFILE}"
+# catch DEBUG envvar, which is passed from pkgbuild::compile_dll()
+elif [ "${DEBUG}" = "true" ]; then
+ PROFILE=dev
+else
+ PROFILE=release
+fi
+
+# e.g. SAVVY_FEATURES="a b" --> "--features 'a b'"
+if [ -n "${SAVVY_FEATURES}" ]; then
+ FEATURE_FLAGS="--features '${SAVVY_FEATURES}'"
+fi
+
+sed \
+ -e "s/@TARGET@/x86_64-pc-windows-gnu/" \
+ -e "s/@PROFILE@/${PROFILE}/" \
+ -e "s/@FEATURE_FLAGS@/${FEATURE_FLAGS}/" \
+ src/Makevars.win.in > src/Makevars.win
diff --git a/r/sedonadb/inst/files/natural-earth_cities_geo.parquet
b/r/sedonadb/inst/files/natural-earth_cities_geo.parquet
new file mode 100644
index 0000000..bc419b4
Binary files /dev/null and
b/r/sedonadb/inst/files/natural-earth_cities_geo.parquet differ
diff --git
a/r/sedonadb/inst/files/natural-earth_countries-geography_geo.parquet
b/r/sedonadb/inst/files/natural-earth_countries-geography_geo.parquet
new file mode 100644
index 0000000..078dc12
Binary files /dev/null and
b/r/sedonadb/inst/files/natural-earth_countries-geography_geo.parquet differ
diff --git a/r/sedonadb/inst/files/natural-earth_countries_geo.parquet
b/r/sedonadb/inst/files/natural-earth_countries_geo.parquet
new file mode 100644
index 0000000..a9f3bd4
Binary files /dev/null and
b/r/sedonadb/inst/files/natural-earth_countries_geo.parquet differ
diff --git a/r/sedonadb/man/as_sedonadb_dataframe.Rd
b/r/sedonadb/man/as_sedonadb_dataframe.Rd
new file mode 100644
index 0000000..b5c3060
--- /dev/null
+++ b/r/sedonadb/man/as_sedonadb_dataframe.Rd
@@ -0,0 +1,25 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/dataframe.R
+\name{as_sedonadb_dataframe}
+\alias{as_sedonadb_dataframe}
+\title{Convert an object to a DataFrame}
+\usage{
+as_sedonadb_dataframe(x, ..., schema = NULL)
+}
+\arguments{
+\item{x}{An object to convert}
+
+\item{...}{Extra arguments passed to/from methods}
+
+\item{schema}{The requested schema}
+}
+\value{
+A sedonadb_dataframe
+}
+\description{
+Convert an object to a DataFrame
+}
+\examples{
+as_sedonadb_dataframe(data.frame(x = 1:3))
+
+}
diff --git a/r/sedonadb/man/figures/README-unnamed-chunk-3-1.png
b/r/sedonadb/man/figures/README-unnamed-chunk-3-1.png
new file mode 100644
index 0000000..3eb52b6
Binary files /dev/null and
b/r/sedonadb/man/figures/README-unnamed-chunk-3-1.png differ
diff --git a/r/sedonadb/man/sd_compute.Rd b/r/sedonadb/man/sd_compute.Rd
new file mode 100644
index 0000000..97590fd
--- /dev/null
+++ b/r/sedonadb/man/sd_compute.Rd
@@ -0,0 +1,29 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/dataframe.R
+\name{sd_compute}
+\alias{sd_compute}
+\alias{sd_collect}
+\title{Collect a DataFrame into memory}
+\usage{
+sd_compute(.data)
+
+sd_collect(.data, ptype = NULL)
+}
+\arguments{
+\item{.data}{A sedonadb_dataframe}
+
+\item{ptype}{The target R object. See
\link[nanoarrow:convert_array_stream]{nanoarrow::convert_array_stream}.}
+}
+\value{
+\code{sd_compute()} returns a sedonadb_dataframe; \code{sd_collect()} returns
+a data.frame (or subclass according to \code{ptype}).
+}
+\description{
+Use \code{sd_compute()} to collect and return the result as a DataFrame;
+use \code{sd_collect()} to collect and return the result as an R data.frame.
+}
+\examples{
+sd_sql("SELECT 1 as one") |> sd_compute()
+sd_sql("SELECT 1 as one") |> sd_collect()
+
+}
diff --git a/r/sedonadb/man/sd_count.Rd b/r/sedonadb/man/sd_count.Rd
new file mode 100644
index 0000000..c93b9d5
--- /dev/null
+++ b/r/sedonadb/man/sd_count.Rd
@@ -0,0 +1,21 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/dataframe.R
+\name{sd_count}
+\alias{sd_count}
+\title{Count rows in a DataFrame}
+\usage{
+sd_count(.data)
+}
+\arguments{
+\item{.data}{A sedonadb_dataframe}
+}
+\value{
+The number of rows after executing the query
+}
+\description{
+Count rows in a DataFrame
+}
+\examples{
+sd_sql("SELECT 1 as one") |> sd_count()
+
+}
diff --git a/r/sedonadb/man/sd_drop_view.Rd b/r/sedonadb/man/sd_drop_view.Rd
new file mode 100644
index 0000000..046b260
--- /dev/null
+++ b/r/sedonadb/man/sd_drop_view.Rd
@@ -0,0 +1,27 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/context.R
+\name{sd_drop_view}
+\alias{sd_drop_view}
+\alias{sd_view}
+\title{Create or Drop a named view}
+\usage{
+sd_drop_view(table_ref)
+
+sd_view(table_ref)
+}
+\arguments{
+\item{table_ref}{The name of the view reference}
+}
+\value{
+The context, invisibly
+}
+\description{
+Remove a view created with \code{\link[=sd_to_view]{sd_to_view()}} from the
context.
+}
+\examples{
+sd_sql("SELECT 1 as one") |> sd_to_view("foofy")
+sd_view("foofy")
+sd_drop_view("foofy")
+try(sd_view("foofy"))
+
+}
diff --git a/r/sedonadb/man/sd_preview.Rd b/r/sedonadb/man/sd_preview.Rd
new file mode 100644
index 0000000..351dd5a
--- /dev/null
+++ b/r/sedonadb/man/sd_preview.Rd
@@ -0,0 +1,32 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/dataframe.R
+\name{sd_preview}
+\alias{sd_preview}
+\title{Preview and print the results of running a query}
+\usage{
+sd_preview(.data, n = NULL, ascii = NULL, width = NULL)
+}
+\arguments{
+\item{.data}{A sedonadb_dataframe}
+
+\item{n}{The number of rows to preview. Use \code{Inf} to preview all rows.
+Defaults to \code{getOption("pillar.print_max")}.}
+
+\item{ascii}{Use \code{TRUE} to force ASCII table formatting or \code{FALSE}
to force
+unicode formatting. By default, use a heuristic to determine if the output
+is unicode-friendly or the value of \code{getOption("cli.unicode")}.}
+
+\item{width}{The character width of the output. Defaults to
+\code{getOption("width")}.}
+}
+\value{
+.data, invisibly
+}
+\description{
+This is used to implement \code{print()} for the sedonadb_dataframe or can
+be used to explicitly preview if \code{options(sedonadb.interactive = FALSE)}.
+}
+\examples{
+sd_sql("SELECT 1 as one") |> sd_preview()
+
+}
diff --git a/r/sedonadb/man/sd_read_parquet.Rd
b/r/sedonadb/man/sd_read_parquet.Rd
new file mode 100644
index 0000000..c370c77
--- /dev/null
+++ b/r/sedonadb/man/sd_read_parquet.Rd
@@ -0,0 +1,22 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/context.R
+\name{sd_read_parquet}
+\alias{sd_read_parquet}
+\title{Create a DataFrame from one or more Parquet files}
+\usage{
+sd_read_parquet(path)
+}
+\arguments{
+\item{path}{One or more paths or URIs to Parquet files}
+}
+\value{
+A sedonadb_dataframe
+}
+\description{
+The query will only be executed when requested.
+}
+\examples{
+path <- system.file("files/natural-earth_cities_geo.parquet", package =
"sedonadb")
+sd_read_parquet(path) |> head(5) |> sd_preview()
+
+}
diff --git a/r/sedonadb/man/sd_sql.Rd b/r/sedonadb/man/sd_sql.Rd
new file mode 100644
index 0000000..3480720
--- /dev/null
+++ b/r/sedonadb/man/sd_sql.Rd
@@ -0,0 +1,21 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/context.R
+\name{sd_sql}
+\alias{sd_sql}
+\title{Create a DataFrame from SQL}
+\usage{
+sd_sql(sql)
+}
+\arguments{
+\item{sql}{A SQL string to execute}
+}
+\value{
+A sedonadb_dataframe
+}
+\description{
+The query will only be executed when requested.
+}
+\examples{
+sd_sql("SELECT ST_Point(0, 1) as geom") |> sd_preview()
+
+}
diff --git a/r/sedonadb/man/sd_to_view.Rd b/r/sedonadb/man/sd_to_view.Rd
new file mode 100644
index 0000000..5c3ab02
--- /dev/null
+++ b/r/sedonadb/man/sd_to_view.Rd
@@ -0,0 +1,27 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/dataframe.R
+\name{sd_to_view}
+\alias{sd_to_view}
+\title{Register a DataFrame as a named view}
+\usage{
+sd_to_view(.data, table_ref, overwrite = FALSE)
+}
+\arguments{
+\item{.data}{A sedonadb_dataframe}
+
+\item{table_ref}{The name of the view reference}
+
+\item{overwrite}{Use TRUE to overwrite a view with the same name (if it
exists)}
+}
+\value{
+.data, invisibly
+}
+\description{
+This is useful for creating a view that can be referenced in a SQL
+statement. Use \code{\link[=sd_drop_view]{sd_drop_view()}} to remove it.
+}
+\examples{
+sd_sql("SELECT 1 as one") |> sd_to_view("foofy")
+sd_sql("SELECT * FROM foofy")
+
+}
diff --git a/r/sedonadb/man/sedonadb-package.Rd
b/r/sedonadb/man/sedonadb-package.Rd
new file mode 100644
index 0000000..6503612
--- /dev/null
+++ b/r/sedonadb/man/sedonadb-package.Rd
@@ -0,0 +1,15 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/sedonadb-package.R
+\docType{package}
+\name{sedonadb-package}
+\alias{sedonadb}
+\alias{sedonadb-package}
+\title{sedonadb: Bindings for Apache SedonaDB}
+\description{
+Provides bindings for Apache SedonaDB, a lightweight query engine optimized
for spatial workflows.
+}
+\author{
+\strong{Maintainer}: Dewey Dunnington \email{[email protected]}
+
+}
+\keyword{internal}
diff --git a/r/sedonadb/man/sedonadb_adbc.Rd b/r/sedonadb/man/sedonadb_adbc.Rd
new file mode 100644
index 0000000..e75e956
--- /dev/null
+++ b/r/sedonadb/man/sedonadb_adbc.Rd
@@ -0,0 +1,26 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/adbc.R
+\name{sedonadb_adbc}
+\alias{sedonadb_adbc}
+\title{SedonaDB ADBC Driver}
+\usage{
+sedonadb_adbc()
+}
+\value{
+An
\code{\link[adbcdrivermanager:adbc_driver_void]{adbcdrivermanager::adbc_driver()}}
of class
+'sedonadb_driver_sedonadb'
+}
+\description{
+SedonaDB ADBC Driver
+}
+\examples{
+library(adbcdrivermanager)
+
+con <- sedonadb_adbc() |>
+ adbc_database_init() |>
+ adbc_connection_init()
+con |>
+ read_adbc("SELECT ST_Point(0, 1) as geometry") |>
+ as.data.frame()
+
+}
diff --git a/r/sedonadb/sedonadb.Rproj b/r/sedonadb/sedonadb.Rproj
new file mode 100644
index 0000000..69fafd4
--- /dev/null
+++ b/r/sedonadb/sedonadb.Rproj
@@ -0,0 +1,22 @@
+Version: 1.0
+
+RestoreWorkspace: No
+SaveWorkspace: No
+AlwaysSaveHistory: Default
+
+EnableCodeIndexing: Yes
+UseSpacesForTab: Yes
+NumSpacesForTab: 2
+Encoding: UTF-8
+
+RnwWeave: Sweave
+LaTeX: pdfLaTeX
+
+AutoAppendNewline: Yes
+StripTrailingWhitespace: Yes
+LineEndingConversion: Posix
+
+BuildType: Package
+PackageUseDevtools: Yes
+PackageInstallArgs: --no-multiarch --with-keep.source
+PackageRoxygenize: rd,collate,namespace
diff --git a/.gitignore b/r/sedonadb/src/.gitignore
similarity index 69%
copy from .gitignore
copy to r/sedonadb/src/.gitignore
index 7441737..e103a0a 100644
--- a/.gitignore
+++ b/r/sedonadb/src/.gitignore
@@ -15,31 +15,10 @@
# specific language governing permissions and limitations
# under the License.
-# MacOS
-.DS_Store
+*.o
+*.so
+*.dll
+target
-# sedona-cli
-.history
-
-# Generated by cargo
-debug/
-target/
-
-# Generated by rustfmt
-**/*.rs.bk
-
-# Generated by cargo llvm-cov
-coverage/
-
-# MSVC Windows builds of rustc generate these, which store debugging
information
-*.pdb
-
-# IDE-specific settings
-.idea/
-.vscode/
-
-# documentation
-site/
-
-# Python cache files
-__pycache__
+Makevars
+Makevars.win
diff --git a/r/sedonadb/src/Makevars.in b/r/sedonadb/src/Makevars.in
new file mode 100644
index 0000000..7828f4c
--- /dev/null
+++ b/r/sedonadb/src/Makevars.in
@@ -0,0 +1,60 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+TARGET = @TARGET@
+
+PROFILE = @PROFILE@
+FEATURE_FLAGS = @FEATURE_FLAGS@
+
+# Add flags if necessary
+RUSTFLAGS =
+
+TARGET_DIR = $(CURDIR)/rust/target
+LIBDIR = $(TARGET_DIR)/$(TARGET)/$(subst dev,debug,$(PROFILE))
+STATLIB = $(LIBDIR)/libsedonadbr.a
+PKG_LIBS = -L$(LIBDIR) -lsedonadbr @PKG_LIBS@
+
+CARGO_BUILD_ARGS = --lib --profile $(PROFILE) $(FEATURE_FLAGS)
--manifest-path=./rust/Cargo.toml --target-dir $(TARGET_DIR)
+
+all: $(SHLIB) clean_intermediate
+
+$(SHLIB): $(STATLIB)
+
+$(STATLIB):
+ # In some environments, ~/.cargo/bin might not be included in PATH, so
we need
+ # to set it here to ensure cargo can be invoked. It is appended to PATH
and
+ # therefore is only used if cargo is absent from the user's PATH.
+ export PATH="$(PATH):$(HOME)/.cargo/bin" && \
+ export CC="$(CC)" && \
+ export CFLAGS="$(CFLAGS)" && \
+ export RUSTFLAGS="$(RUSTFLAGS)" && \
+ if [ "$(TARGET)" != "wasm32-unknown-emscripten" ]; then \
+ cargo build $(CARGO_BUILD_ARGS); \
+ else \
+ export CARGO_PROFILE_DEV_PANIC="abort" && \
+ export CARGO_PROFILE_RELEASE_PANIC="abort" && \
+ export RUSTFLAGS="$(RUSTFLAGS) -Zdefault-visibility=hidden" && \
+ cargo +nightly build $(CARGO_BUILD_ARGS) --target $(TARGET)
-Zbuild-std=panic_abort,std; \
+ fi
+
+clean_intermediate: $(SHLIB)
+ rm -f $(STATLIB)
+
+clean:
+ rm -Rf $(SHLIB) $(OBJECTS) $(STATLIB) ./rust/target
+
+.PHONY: all clean_intermediate clean
diff --git a/r/sedonadb/src/Makevars.win.in b/r/sedonadb/src/Makevars.win.in
new file mode 100644
index 0000000..a9f15a4
--- /dev/null
+++ b/r/sedonadb/src/Makevars.win.in
@@ -0,0 +1,41 @@
+TARGET = @TARGET@
+
+PROFILE = @PROFILE@
+FEATURE_FLAGS = @FEATURE_FLAGS@
+
+# Add flags if necessary
+RUSTFLAGS =
+
+TARGET_DIR = $(CURDIR)/rust/target
+LIBDIR = $(TARGET_DIR)/$(TARGET)/$(subst dev,debug,$(PROFILE))
+STATLIB = $(LIBDIR)/libsedonadb.a
+PKG_LIBS = -L$(LIBDIR) -lsedonadb -lws2_32 -ladvapi32 -luserenv -lbcrypt
-lntdll
+
+# Rtools doesn't have the linker in the location that cargo expects, so we need
+# to overwrite it via configuration.
+CARGO_LINKER = x86_64-w64-mingw32.static.posix-gcc.exe
+
+all: $(SHLIB) clean_intermediate
+
+$(SHLIB): $(STATLIB)
+
+$(STATLIB):
+ # When the GNU toolchain is used (i.e. on CRAN), -lgcc_eh is specified
for
+ # building proc-macro2, but Rtools doesn't contain libgcc_eh. This
isn't used
+ # in actual, but we need this tweak to please the compiler.
+ mkdir -p $(LIBDIR)/libgcc_mock && touch
$(LIBDIR)/libgcc_mock/libgcc_eh.a
+
+ export CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER="$(CARGO_LINKER)" && \
+ export LIBRARY_PATH="$${LIBRARY_PATH};$(LIBDIR)/libgcc_mock" && \
+ export CC="$(CC)" && \
+ export CFLAGS="$(CFLAGS)" && \
+ export RUSTFLAGS="$(RUSTFLAGS)" && \
+ cargo build --target $(TARGET) --lib --profile $(PROFILE)
$(FEATURE_FLAGS) --manifest-path ./rust/Cargo.toml --target-dir $(TARGET_DIR)
+
+clean_intermediate: $(SHLIB)
+ rm -f $(STATLIB)
+
+clean:
+ rm -Rf $(SHLIB) $(OBJECTS) $(STATLIB) ./rust/target
+
+.PHONY: all clean_intermediate clean
diff --git a/r/sedonadb/src/init.c b/r/sedonadb/src/init.c
new file mode 100644
index 0000000..2434cda
--- /dev/null
+++ b/r/sedonadb/src/init.c
@@ -0,0 +1,201 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include <Rinternals.h>
+
+#include <R_ext/Parse.h>
+#include <stdint.h>
+
+#include "rust/api.h"
+
+static uintptr_t TAGGED_POINTER_MASK = (uintptr_t)1;
+
+SEXP handle_result(SEXP res_) {
+ uintptr_t res = (uintptr_t)res_;
+
+ // An error is indicated by tag.
+ if ((res & TAGGED_POINTER_MASK) == 1) {
+ // Remove tag
+ SEXP res_aligned = (SEXP)(res & ~TAGGED_POINTER_MASK);
+
+ // Currently, there are two types of error cases:
+ //
+ // 1. Error from Rust code
+ // 2. Error from R's C API, which is caught by R_UnwindProtect()
+ //
+ if (TYPEOF(res_aligned) == CHARSXP) {
+ // In case 1, the result is an error message that can be passed to
+ // Rf_errorcall() directly.
+ Rf_errorcall(R_NilValue, "%s", CHAR(res_aligned));
+ } else {
+ // In case 2, the result is the token to restart the
+ // cleanup process on R's side.
+ R_ContinueUnwind(res_aligned);
+ }
+ }
+
+ return (SEXP)res;
+}
+
+SEXP savvy_init_r_runtime__impl(DllInfo *c_arg___dll_info) {
+ SEXP res = savvy_init_r_runtime__ffi(c_arg___dll_info);
+ return handle_result(res);
+}
+
+SEXP savvy_init_r_runtime_interrupts__impl(SEXP c_arg__interrupts_call,
+ SEXP c_arg__pkg_env) {
+ SEXP res = savvy_init_r_runtime_interrupts__ffi(c_arg__interrupts_call,
+ c_arg__pkg_env);
+ return handle_result(res);
+}
+
+SEXP savvy_sedonadb_adbc_init_func__impl(void) {
+ SEXP res = savvy_sedonadb_adbc_init_func__ffi();
+ return handle_result(res);
+}
+
+SEXP savvy_InternalContext_data_frame_from_array_stream__impl(
+ SEXP self__, SEXP c_arg__stream_xptr, SEXP c_arg__collect_now) {
+ SEXP res = savvy_InternalContext_data_frame_from_array_stream__ffi(
+ self__, c_arg__stream_xptr, c_arg__collect_now);
+ return handle_result(res);
+}
+
+SEXP savvy_InternalContext_deregister_table__impl(SEXP self__,
+ SEXP c_arg__table_ref) {
+ SEXP res =
+ savvy_InternalContext_deregister_table__ffi(self__, c_arg__table_ref);
+ return handle_result(res);
+}
+
+SEXP savvy_InternalContext_new__impl(void) {
+ SEXP res = savvy_InternalContext_new__ffi();
+ return handle_result(res);
+}
+
+SEXP savvy_InternalContext_read_parquet__impl(SEXP self__, SEXP c_arg__paths) {
+ SEXP res = savvy_InternalContext_read_parquet__ffi(self__, c_arg__paths);
+ return handle_result(res);
+}
+
+SEXP savvy_InternalContext_sql__impl(SEXP self__, SEXP c_arg__query) {
+ SEXP res = savvy_InternalContext_sql__ffi(self__, c_arg__query);
+ return handle_result(res);
+}
+
+SEXP savvy_InternalContext_view__impl(SEXP self__, SEXP c_arg__table_ref) {
+ SEXP res = savvy_InternalContext_view__ffi(self__, c_arg__table_ref);
+ return handle_result(res);
+}
+
+SEXP savvy_InternalDataFrame_collect__impl(SEXP self__, SEXP c_arg__out) {
+ SEXP res = savvy_InternalDataFrame_collect__ffi(self__, c_arg__out);
+ return handle_result(res);
+}
+
+SEXP savvy_InternalDataFrame_compute__impl(SEXP self__, SEXP c_arg__ctx) {
+ SEXP res = savvy_InternalDataFrame_compute__ffi(self__, c_arg__ctx);
+ return handle_result(res);
+}
+
+SEXP savvy_InternalDataFrame_count__impl(SEXP self__) {
+ SEXP res = savvy_InternalDataFrame_count__ffi(self__);
+ return handle_result(res);
+}
+
+SEXP savvy_InternalDataFrame_limit__impl(SEXP self__, SEXP c_arg__n) {
+ SEXP res = savvy_InternalDataFrame_limit__ffi(self__, c_arg__n);
+ return handle_result(res);
+}
+
+SEXP savvy_InternalDataFrame_primary_geometry_column_index__impl(SEXP self__) {
+ SEXP res =
savvy_InternalDataFrame_primary_geometry_column_index__ffi(self__);
+ return handle_result(res);
+}
+
+SEXP savvy_InternalDataFrame_show__impl(SEXP self__, SEXP c_arg__ctx,
+ SEXP c_arg__width_chars,
+ SEXP c_arg__ascii, SEXP c_arg__limit) {
+ SEXP res = savvy_InternalDataFrame_show__ffi(
+ self__, c_arg__ctx, c_arg__width_chars, c_arg__ascii, c_arg__limit);
+ return handle_result(res);
+}
+
+SEXP savvy_InternalDataFrame_to_arrow_schema__impl(SEXP self__,
+ SEXP c_arg__out) {
+ SEXP res = savvy_InternalDataFrame_to_arrow_schema__ffi(self__, c_arg__out);
+ return handle_result(res);
+}
+
+SEXP savvy_InternalDataFrame_to_arrow_stream__impl(SEXP self__,
+ SEXP c_arg__out) {
+ SEXP res = savvy_InternalDataFrame_to_arrow_stream__ffi(self__, c_arg__out);
+ return handle_result(res);
+}
+
+SEXP savvy_InternalDataFrame_to_view__impl(SEXP self__, SEXP c_arg__ctx,
+ SEXP c_arg__table_ref,
+ SEXP c_arg__overwrite) {
+ SEXP res = savvy_InternalDataFrame_to_view__ffi(
+ self__, c_arg__ctx, c_arg__table_ref, c_arg__overwrite);
+ return handle_result(res);
+}
+
+static const R_CallMethodDef CallEntries[] = {
+ {"savvy_init_r_runtime_interrupts__impl",
+ (DL_FUNC)&savvy_init_r_runtime_interrupts__impl, 2},
+ {"savvy_sedonadb_adbc_init_func__impl",
+ (DL_FUNC)&savvy_sedonadb_adbc_init_func__impl, 0},
+ {"savvy_InternalContext_data_frame_from_array_stream__impl",
+ (DL_FUNC)&savvy_InternalContext_data_frame_from_array_stream__impl, 3},
+ {"savvy_InternalContext_deregister_table__impl",
+ (DL_FUNC)&savvy_InternalContext_deregister_table__impl, 2},
+ {"savvy_InternalContext_new__impl",
+ (DL_FUNC)&savvy_InternalContext_new__impl, 0},
+ {"savvy_InternalContext_read_parquet__impl",
+ (DL_FUNC)&savvy_InternalContext_read_parquet__impl, 2},
+ {"savvy_InternalContext_sql__impl",
+ (DL_FUNC)&savvy_InternalContext_sql__impl, 2},
+ {"savvy_InternalContext_view__impl",
+ (DL_FUNC)&savvy_InternalContext_view__impl, 2},
+ {"savvy_InternalDataFrame_collect__impl",
+ (DL_FUNC)&savvy_InternalDataFrame_collect__impl, 2},
+ {"savvy_InternalDataFrame_compute__impl",
+ (DL_FUNC)&savvy_InternalDataFrame_compute__impl, 2},
+ {"savvy_InternalDataFrame_count__impl",
+ (DL_FUNC)&savvy_InternalDataFrame_count__impl, 1},
+ {"savvy_InternalDataFrame_limit__impl",
+ (DL_FUNC)&savvy_InternalDataFrame_limit__impl, 2},
+ {"savvy_InternalDataFrame_primary_geometry_column_index__impl",
+ (DL_FUNC)&savvy_InternalDataFrame_primary_geometry_column_index__impl, 1},
+ {"savvy_InternalDataFrame_show__impl",
+ (DL_FUNC)&savvy_InternalDataFrame_show__impl, 5},
+ {"savvy_InternalDataFrame_to_arrow_schema__impl",
+ (DL_FUNC)&savvy_InternalDataFrame_to_arrow_schema__impl, 2},
+ {"savvy_InternalDataFrame_to_arrow_stream__impl",
+ (DL_FUNC)&savvy_InternalDataFrame_to_arrow_stream__impl, 2},
+ {"savvy_InternalDataFrame_to_view__impl",
+ (DL_FUNC)&savvy_InternalDataFrame_to_view__impl, 4},
+ {NULL, NULL, 0}};
+
+void R_init_sedonadb(DllInfo *dll) {
+ R_registerRoutines(dll, NULL, CallEntries, NULL, NULL);
+ R_useDynamicSymbols(dll, FALSE);
+
+ // Functions for initialization, if any.
+ savvy_init_r_runtime__impl(dll);
+}
diff --git a/r/sedonadb/src/rust/.cargo/config.toml
b/r/sedonadb/src/rust/.cargo/config.toml
new file mode 100644
index 0000000..3d8bdda
--- /dev/null
+++ b/r/sedonadb/src/rust/.cargo/config.toml
@@ -0,0 +1,10 @@
+# On Windows, link.exe fails when the artifact contains unresolved symbols
+# (i.e., R's API, which cannot be used without a real R session). This option
+# makes the linker ignore these problems.
+#
+# This setting is needed only when you run `cargo test`, not when `R CMD check`
+# etc. The `.cargo` directory need to be excluded on building the package (i.e.
+# add `^src/rust/\.cargo$` to `.Rbuildignore`) because otherwise you'll get the
+# "hidden files and directories" NOTE.
+[target.x86_64-pc-windows-msvc]
+rustflags = ["-C", "link-arg=/FORCE:UNRESOLVED"]
diff --git a/.gitignore b/r/sedonadb/src/rust/Cargo.toml
similarity index 55%
copy from .gitignore
copy to r/sedonadb/src/rust/Cargo.toml
index 7441737..645fa23 100644
--- a/.gitignore
+++ b/r/sedonadb/src/rust/Cargo.toml
@@ -15,31 +15,25 @@
# specific language governing permissions and limitations
# under the License.
-# MacOS
-.DS_Store
-
-# sedona-cli
-.history
-
-# Generated by cargo
-debug/
-target/
-
-# Generated by rustfmt
-**/*.rs.bk
-
-# Generated by cargo llvm-cov
-coverage/
-
-# MSVC Windows builds of rustc generate these, which store debugging
information
-*.pdb
-
-# IDE-specific settings
-.idea/
-.vscode/
-
-# documentation
-site/
-
-# Python cache files
-__pycache__
+[package]
+name = "sedonadbr"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+crate-type = ["staticlib", "lib"]
+
+[dependencies]
+arrow-schema = { workspace = true }
+arrow-array = { workspace = true }
+datafusion = { workspace = true }
+datafusion-common = { workspace = true }
+savvy = "*"
+savvy-ffi = "*"
+sedona = { path = "../../../../rust/sedona" }
+sedona-adbc = { path = "../../../../rust/sedona-adbc" }
+sedona-expr = { path = "../../../../rust/sedona-expr" }
+sedona-geoparquet = { path = "../../../../rust/sedona-geoparquet" }
+sedona-schema = { path = "../../../../rust/sedona-schema" }
+thiserror = { workspace = true }
+tokio = { workspace = true }
diff --git a/r/sedonadb/src/rust/api.h b/r/sedonadb/src/rust/api.h
new file mode 100644
index 0000000..4138988
--- /dev/null
+++ b/r/sedonadb/src/rust/api.h
@@ -0,0 +1,46 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+SEXP savvy_init_r_runtime__ffi(DllInfo *c_arg___dll_info);
+SEXP savvy_init_r_runtime_interrupts__ffi(SEXP c_arg__interrupts_call,
+ SEXP c_arg__pkg_env);
+SEXP savvy_sedonadb_adbc_init_func__ffi(void);
+
+// methods and associated functions for InternalContext
+SEXP savvy_InternalContext_data_frame_from_array_stream__ffi(
+ SEXP self__, SEXP c_arg__stream_xptr, SEXP c_arg__collect_now);
+SEXP savvy_InternalContext_deregister_table__ffi(SEXP self__,
+ SEXP c_arg__table_ref);
+SEXP savvy_InternalContext_new__ffi(void);
+SEXP savvy_InternalContext_read_parquet__ffi(SEXP self__, SEXP c_arg__paths);
+SEXP savvy_InternalContext_sql__ffi(SEXP self__, SEXP c_arg__query);
+SEXP savvy_InternalContext_view__ffi(SEXP self__, SEXP c_arg__table_ref);
+
+// methods and associated functions for InternalDataFrame
+SEXP savvy_InternalDataFrame_collect__ffi(SEXP self__, SEXP c_arg__out);
+SEXP savvy_InternalDataFrame_compute__ffi(SEXP self__, SEXP c_arg__ctx);
+SEXP savvy_InternalDataFrame_count__ffi(SEXP self__);
+SEXP savvy_InternalDataFrame_limit__ffi(SEXP self__, SEXP c_arg__n);
+SEXP savvy_InternalDataFrame_primary_geometry_column_index__ffi(SEXP self__);
+SEXP savvy_InternalDataFrame_show__ffi(SEXP self__, SEXP c_arg__ctx,
+ SEXP c_arg__width_chars,
+ SEXP c_arg__ascii, SEXP c_arg__limit);
+SEXP savvy_InternalDataFrame_to_arrow_schema__ffi(SEXP self__, SEXP
c_arg__out);
+SEXP savvy_InternalDataFrame_to_arrow_stream__ffi(SEXP self__, SEXP
c_arg__out);
+SEXP savvy_InternalDataFrame_to_view__ffi(SEXP self__, SEXP c_arg__ctx,
+ SEXP c_arg__table_ref,
+ SEXP c_arg__overwrite);
diff --git a/r/sedonadb/src/rust/src/context.rs
b/r/sedonadb/src/rust/src/context.rs
new file mode 100644
index 0000000..67f52a3
--- /dev/null
+++ b/r/sedonadb/src/rust/src/context.rs
@@ -0,0 +1,124 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+use std::sync::Arc;
+
+use arrow_array::{
+ ffi_stream::{ArrowArrayStreamReader, FFI_ArrowArrayStream},
+ RecordBatchReader,
+};
+use arrow_schema::ArrowError;
+use datafusion::catalog::{MemTable, TableProvider};
+use savvy::{savvy, savvy_err, Result};
+
+use sedona::{context::SedonaContext,
record_batch_reader_provider::RecordBatchReaderProvider};
+use sedona_geoparquet::provider::GeoParquetReadOptions;
+use tokio::runtime::Runtime;
+
+use crate::{
+ dataframe::{new_data_frame, InternalDataFrame},
+ runtime::wait_for_future_captured_r,
+};
+
+#[savvy]
+pub struct InternalContext {
+ pub inner: Arc<SedonaContext>,
+ pub runtime: Arc<Runtime>,
+}
+
+#[savvy]
+impl InternalContext {
+ pub fn new() -> Result<Self> {
+ let runtime = tokio::runtime::Builder::new_multi_thread()
+ .enable_all()
+ .build()?;
+
+ let inner = wait_for_future_captured_r(&runtime,
SedonaContext::new_local_interactive())??;
+
+ Ok(Self {
+ inner: Arc::new(inner),
+ runtime: Arc::new(runtime),
+ })
+ }
+
+ pub fn read_parquet(&self, paths: savvy::Sexp) ->
Result<InternalDataFrame> {
+ let paths_strsxp = savvy::StringSexp::try_from(paths)?;
+ let table_paths = paths_strsxp
+ .iter()
+ .map(|s| s.to_string())
+ .collect::<Vec<_>>();
+
+ let inner_context = self.inner.clone();
+ let inner = wait_for_future_captured_r(&self.runtime, async move {
+ inner_context
+ .read_parquet(table_paths, GeoParquetReadOptions::default())
+ .await
+ })??;
+
+ Ok(new_data_frame(inner, self.runtime.clone()))
+ }
+
+ pub fn sql(&self, query: &str) -> Result<InternalDataFrame> {
+ let query_string = query.to_string();
+ let inner_context = self.inner.clone();
+ let inner = wait_for_future_captured_r(&self.runtime, async move {
+ inner_context.sql(&query_string).await
+ })??;
+ Ok(new_data_frame(inner, self.runtime.clone()))
+ }
+
+ pub fn view(&self, table_ref: &str) -> Result<InternalDataFrame> {
+ let inner_context = self.inner.clone();
+ let table_ref_string = table_ref.to_string();
+ let inner = wait_for_future_captured_r(&self.runtime, async move {
+ inner_context.ctx.table(table_ref_string).await
+ })??;
+ Ok(new_data_frame(inner, self.runtime.clone()))
+ }
+
+ pub fn data_frame_from_array_stream(
+ &self,
+ stream_xptr: savvy::Sexp,
+ collect_now: bool,
+ ) -> savvy::Result<InternalDataFrame> {
+ let ffi_stream =
+ unsafe { savvy_ffi::R_ExternalPtrAddr(stream_xptr.0) as *mut
FFI_ArrowArrayStream };
+ if ffi_stream.is_null() {
+ return Err(savvy_err!("external pointer to null in
to_arrow_schema()"));
+ }
+
+ let stream = unsafe { FFI_ArrowArrayStream::from_raw(ffi_stream as _)
};
+ let stream_reader = ArrowArrayStreamReader::try_new(stream)?;
+
+ // Some readers are sensitive to being collected on the R thread or
not, so
+ // provide the option to collect everything immediately.
+ let provider: Arc<dyn TableProvider> = if collect_now {
+ let schema = stream_reader.schema();
+ let batches = stream_reader.collect::<std::result::Result<Vec<_>,
ArrowError>>()?;
+ Arc::new(MemTable::try_new(schema, vec![batches])?)
+ } else {
+ Arc::new(RecordBatchReaderProvider::new(Box::new(stream_reader)))
+ };
+
+ let inner = self.inner.ctx.read_table(provider)?;
+ Ok(new_data_frame(inner, self.runtime.clone()))
+ }
+
+ pub fn deregister_table(&self, table_ref: &str) -> savvy::Result<()> {
+ self.inner.ctx.deregister_table(table_ref)?;
+ Ok(())
+ }
+}
diff --git a/r/sedonadb/src/rust/src/dataframe.rs
b/r/sedonadb/src/rust/src/dataframe.rs
new file mode 100644
index 0000000..274aa49
--- /dev/null
+++ b/r/sedonadb/src/rust/src/dataframe.rs
@@ -0,0 +1,194 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+use std::ptr::swap_nonoverlapping;
+use std::sync::Arc;
+
+use arrow_array::ffi::FFI_ArrowSchema;
+use arrow_array::ffi_stream::FFI_ArrowArrayStream;
+use arrow_array::{RecordBatchIterator, RecordBatchReader};
+use datafusion::catalog::MemTable;
+use datafusion::prelude::DataFrame;
+use savvy::{savvy, savvy_err, Result};
+use sedona::context::SedonaDataFrame;
+use sedona::reader::SedonaStreamReader;
+use sedona::show::{DisplayMode, DisplayTableOptions};
+use sedona_schema::schema::SedonaSchema;
+use tokio::runtime::Runtime;
+
+use crate::context::InternalContext;
+use crate::runtime::wait_for_future_captured_r;
+
+#[savvy]
+pub struct InternalDataFrame {
+ pub inner: DataFrame,
+ pub runtime: Arc<Runtime>,
+}
+
+pub fn new_data_frame(inner: DataFrame, runtime: Arc<Runtime>) ->
InternalDataFrame {
+ InternalDataFrame { inner, runtime }
+}
+
+#[savvy]
+impl InternalDataFrame {
+ fn limit(&self, n: f64) -> Result<InternalDataFrame> {
+ let inner = self.inner.clone().limit(0, Some(n.floor() as usize))?;
+ Ok(InternalDataFrame {
+ inner,
+ runtime: self.runtime.clone(),
+ })
+ }
+
+ fn count(&self) -> Result<savvy::Sexp> {
+ let inner = self.inner.clone();
+ let counted =
+ wait_for_future_captured_r(&self.runtime, async move {
inner.count().await })??;
+
+ let counted_double = counted as f64;
+ savvy::Sexp::try_from(counted_double)
+ }
+
+ fn primary_geometry_column_index(&self) -> Result<savvy::Sexp> {
+ if let Some(col) =
self.inner.schema().primary_geometry_column_index()? {
+ Ok(unsafe {
savvy::Sexp(savvy_ffi::Rf_ScalarInteger(col.try_into()?)) })
+ } else {
+ Ok(savvy::NullSexp.into())
+ }
+ }
+
+ fn to_arrow_schema(&self, out: savvy::Sexp) -> Result<()> {
+ let out_void = unsafe { savvy_ffi::R_ExternalPtrAddr(out.0) };
+ if out_void.is_null() {
+ return Err(savvy_err!("external pointer to null in
to_arrow_schema()"));
+ }
+
+ let schema_no_qualifiers =
self.inner.schema().clone().strip_qualifiers();
+ let schema = schema_no_qualifiers.as_arrow();
+ let mut ffi_schema = FFI_ArrowSchema::try_from(schema)?;
+ let ffi_out = out_void as *mut FFI_ArrowSchema;
+ unsafe { swap_nonoverlapping(&mut ffi_schema, ffi_out, 1) };
+ Ok(())
+ }
+
+ fn to_arrow_stream(&self, out: savvy::Sexp) -> Result<()> {
+ let out_void = unsafe { savvy_ffi::R_ExternalPtrAddr(out.0) };
+ if out_void.is_null() {
+ return Err(savvy_err!("external pointer to null in
to_arrow_stream()"));
+ }
+
+ let inner = self.inner.clone();
+ let stream =
+ wait_for_future_captured_r(
+ &self.runtime,
+ async move { inner.execute_stream().await },
+ )??;
+
+ let reader = SedonaStreamReader::new(self.runtime.clone(), stream);
+ let reader: Box<dyn RecordBatchReader + Send> = Box::new(reader);
+
+ let mut ffi_stream = FFI_ArrowArrayStream::new(reader);
+ let ffi_out = out_void as *mut FFI_ArrowArrayStream;
+ unsafe { swap_nonoverlapping(&mut ffi_stream, ffi_out, 1) };
+
+ Ok(())
+ }
+
+ fn compute(&self, ctx: &InternalContext) -> Result<InternalDataFrame> {
+ let schema = self.inner.schema();
+ let batches =
+ wait_for_future_captured_r(&self.runtime,
self.inner.clone().collect_partitioned())??;
+ let provider = Arc::new(MemTable::try_new(schema.clone().into(),
batches)?);
+ let inner = ctx.inner.ctx.read_table(provider)?;
+ Ok(new_data_frame(inner, self.runtime.clone()))
+ }
+
+ fn collect(&self, out: savvy::Sexp) -> Result<savvy::Sexp> {
+ let out_void = unsafe { savvy_ffi::R_ExternalPtrAddr(out.0) };
+ if out_void.is_null() {
+ return Err(savvy_err!("external pointer to null in collect()"));
+ }
+
+ let inner = self.inner.clone();
+ let batches =
+ wait_for_future_captured_r(&self.runtime, async move {
inner.collect().await })??;
+
+ let size: usize = batches.iter().map(|batch| batch.num_rows()).sum();
+
+ let reader: Box<dyn RecordBatchReader + Send> = if batches.is_empty() {
+ let schema_no_qualifiers =
self.inner.schema().clone().strip_qualifiers();
+ let schema = schema_no_qualifiers.as_arrow();
+ Box::new(RecordBatchIterator::new(
+ vec![].into_iter(),
+ Arc::new(schema.clone()),
+ ))
+ } else {
+ let schema = batches[0].schema();
+ Box::new(RecordBatchIterator::new(
+ batches.into_iter().map(Ok),
+ schema,
+ ))
+ };
+
+ let mut ffi_stream = FFI_ArrowArrayStream::new(reader);
+ let ffi_out = out_void as *mut FFI_ArrowArrayStream;
+ unsafe { swap_nonoverlapping(&mut ffi_stream, ffi_out, 1) };
+
+ savvy::Sexp::try_from(size as f64)
+ }
+
+ fn to_view(&self, ctx: &InternalContext, table_ref: &str, overwrite: bool)
-> Result<()> {
+ let provider = self.inner.clone().into_view();
+ if overwrite && ctx.inner.ctx.table_exist(table_ref)? {
+ ctx.deregister_table(table_ref)?;
+ }
+
+ ctx.inner.ctx.register_table(table_ref, provider)?;
+ Ok(())
+ }
+
+ fn show(
+ &self,
+ ctx: &InternalContext,
+ width_chars: i32,
+ ascii: bool,
+ limit: Option<f64>,
+ ) -> Result<savvy::Sexp> {
+ let mut options = DisplayTableOptions::new();
+ options.table_width = width_chars.try_into().unwrap_or(u16::MAX);
+ options.arrow_options = options.arrow_options.with_types_info(true);
+ if !ascii {
+ options.display_mode = DisplayMode::Utf8;
+ }
+
+ let inner = self.inner.clone();
+ let inner_context = ctx.inner.clone();
+ let limit_usize = limit.map(|value| {
+ if value > i32::MAX as f64 {
+ i32::MAX as usize
+ } else {
+ value as usize
+ }
+ });
+
+ let out_string = wait_for_future_captured_r(&self.runtime, async move {
+ inner
+ .show_sedona(&inner_context, limit_usize, options)
+ .await
+ })??;
+
+ savvy::Sexp::try_from(out_string)
+ }
+}
diff --git a/r/sedonadb/src/rust/src/error.rs b/r/sedonadb/src/rust/src/error.rs
new file mode 100644
index 0000000..645d336
--- /dev/null
+++ b/r/sedonadb/src/rust/src/error.rs
@@ -0,0 +1,58 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+use arrow_schema::ArrowError;
+use datafusion_common::DataFusionError;
+
+use thiserror::Error;
+
+#[derive(Error, Debug)]
+pub enum RSedonaError {
+ #[error("{0}")]
+ DF(Box<DataFusionError>),
+ #[error("{0}")]
+ TokioIO(tokio::io::Error),
+ #[error("{0}")]
+ TokioJoin(tokio::task::JoinError),
+ #[error("{0}")]
+ Internal(String),
+ #[error("Interrupted")]
+ Interrupted,
+}
+
+impl From<DataFusionError> for RSedonaError {
+ fn from(other: DataFusionError) -> Self {
+ RSedonaError::DF(Box::new(other))
+ }
+}
+
+impl From<tokio::io::Error> for RSedonaError {
+ fn from(other: tokio::io::Error) -> Self {
+ RSedonaError::TokioIO(other)
+ }
+}
+
+impl From<tokio::task::JoinError> for RSedonaError {
+ fn from(other: tokio::task::JoinError) -> Self {
+ RSedonaError::TokioJoin(other)
+ }
+}
+
+impl From<ArrowError> for RSedonaError {
+ fn from(other: ArrowError) -> Self {
+ RSedonaError::DF(Box::new(DataFusionError::ArrowError(Box::new(other),
None)))
+ }
+}
diff --git a/r/sedonadb/src/rust/src/lib.rs b/r/sedonadb/src/rust/src/lib.rs
new file mode 100644
index 0000000..edc2913
--- /dev/null
+++ b/r/sedonadb/src/rust/src/lib.rs
@@ -0,0 +1,42 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+// Example functions
+
+use std::ffi::c_void;
+
+use savvy::savvy;
+
+use savvy_ffi::R_NilValue;
+use sedona_adbc::AdbcSedonadbDriverInit;
+
+mod context;
+mod dataframe;
+mod error;
+mod runtime;
+
+#[savvy]
+fn sedonadb_adbc_init_func() -> savvy::Result<savvy::Sexp> {
+ let driver_init_void = AdbcSedonadbDriverInit as *mut c_void;
+
+ unsafe {
+ Ok(savvy::Sexp(savvy_ffi::R_MakeExternalPtr(
+ driver_init_void,
+ R_NilValue,
+ R_NilValue,
+ )))
+ }
+}
diff --git a/r/sedonadb/src/rust/src/runtime.rs
b/r/sedonadb/src/rust/src/runtime.rs
new file mode 100644
index 0000000..fb1c535
--- /dev/null
+++ b/r/sedonadb/src/rust/src/runtime.rs
@@ -0,0 +1,153 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+use std::{
+ future::Future,
+ sync::{OnceLock, RwLock},
+ time::Duration,
+};
+
+use savvy::{savvy, savvy_err, savvy_init};
+use savvy_ffi::R_NilValue;
+use tokio::{runtime::Runtime, time::sleep};
+
+use crate::error::RSedonaError;
+
+pub fn wait_for_future_captured_r<F>(runtime: &Runtime, fut: F) ->
Result<F::Output, RSedonaError>
+where
+ F: Future + Send + 'static,
+ F::Output: Send,
+{
+ const INTERVAL_CHECK_SIGNALS: Duration = Duration::from_millis(1_000);
+ let handle = runtime.spawn(async move {
+ tokio::pin!(fut);
+ loop {
+ tokio::select! {
+ res = &mut fut => break Ok(res),
+ _ = sleep(INTERVAL_CHECK_SIGNALS) => {
+ let handle = R.get().unwrap().rt().spawn(async move {
+ R.get().unwrap().check_signals()
+ });
+ handle.await??;
+ }
+ }
+ }
+ });
+
+ R.get().unwrap().rt().block_on(handle)?
+}
+
+struct RRuntime {
+ rt: Runtime,
+ check_interrupts_call: RwLock<Option<savvy::Sexp>>,
+ pkg_env: RwLock<Option<savvy::Sexp>>,
+}
+
+unsafe impl Send for RRuntime {}
+
+unsafe impl Sync for RRuntime {}
+
+impl RRuntime {
+ pub fn try_new() -> Result<Self, RSedonaError> {
+ let rt = tokio::runtime::Builder::new_current_thread()
+ .enable_all()
+ .build()?;
+ Ok(Self {
+ rt,
+ check_interrupts_call: RwLock::new(None),
+ pkg_env: RwLock::new(None),
+ })
+ }
+
+ pub fn rt(&self) -> &Runtime {
+ &self.rt
+ }
+
+ pub fn check_signals(&self) -> Result<(), RSedonaError> {
+ let (maybe_call, maybe_env) = match (
+ self.check_interrupts_call.try_read(),
+ self.pkg_env.try_read(),
+ ) {
+ (Ok(call), Ok(env)) => (call, env),
+ _ => {
+ return Err(RSedonaError::Internal(
+ "Check interrupts call could not be read".to_string(),
+ ));
+ }
+ };
+
+ let (call, env) = match (maybe_call.as_ref(), maybe_env.as_ref()) {
+ (Some(call), Some(env)) => (call, env),
+ _ => {
+ return Err(RSedonaError::Internal(
+ "Check interrupts not set".to_string(),
+ ));
+ }
+ };
+
+ unsafe {
+ // We could save this error for future things that can actually
fail (like evaluating
+ // R UDFs)
+ let is_interrupted_sexp = savvy::unwind_protect(||
savvy_ffi::Rf_eval(call.0, env.0))
+ .expect(
+ "Check interrupts function must not error (i.e., must be
wrapped in tryCatch())",
+ );
+
+ let is_interrupted: bool = savvy::Sexp(is_interrupted_sexp)
+ .try_into()
+ .expect("Check interrupts function must return a bool");
+
+ if is_interrupted {
+ Err(RSedonaError::Interrupted)
+ } else {
+ Ok(())
+ }
+ }
+ }
+}
+
+static R: OnceLock<RRuntime> = OnceLock::new();
+
+#[savvy_init]
+fn init_r_runtime(_dll_info: *mut savvy_ffi::DllInfo) -> savvy::Result<()> {
+ R.get_or_init(|| RRuntime::try_new().unwrap());
+ Ok(())
+}
+
+#[savvy]
+fn init_r_runtime_interrupts(
+ interrupts_call: savvy::Sexp,
+ pkg_env: savvy::Sexp,
+) -> savvy::Result<()> {
+ if let Some(r) = R.get() {
+ unsafe {
+ savvy::unwind_protect(|| {
+ savvy_ffi::R_PreserveObject(interrupts_call.0);
+ savvy_ffi::R_PreserveObject(pkg_env.0);
+ R_NilValue
+ })?;
+ }
+
+ r.check_interrupts_call
+ .try_write()?
+ .replace(interrupts_call);
+ r.pkg_env.try_write()?.replace(pkg_env);
+
+ Ok(())
+ } else {
+ Err(savvy_err!("R runtime not initialized"))
+ }
+}
diff --git a/r/sedonadb/src/sedonadb-win.def b/r/sedonadb/src/sedonadb-win.def
new file mode 100644
index 0000000..0a809fa
--- /dev/null
+++ b/r/sedonadb/src/sedonadb-win.def
@@ -0,0 +1,2 @@
+EXPORTS
+R_init_sedonadb
diff --git a/.gitignore b/r/sedonadb/tests/testthat.R
similarity index 66%
copy from .gitignore
copy to r/sedonadb/tests/testthat.R
index 7441737..fb41f2b 100644
--- a/.gitignore
+++ b/r/sedonadb/tests/testthat.R
@@ -14,32 +14,15 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
+# This file is part of the standard setup for testthat.
+# It is recommended that you do not modify it.
+#
+# Where should you do additional test configuration?
+# Learn more about the roles of various files in:
+# * https://r-pkgs.org/testing-design.html#sec-tests-files-overview
+# * https://testthat.r-lib.org/articles/special-files.html
-# MacOS
-.DS_Store
-
-# sedona-cli
-.history
-
-# Generated by cargo
-debug/
-target/
-
-# Generated by rustfmt
-**/*.rs.bk
-
-# Generated by cargo llvm-cov
-coverage/
-
-# MSVC Windows builds of rustc generate these, which store debugging
information
-*.pdb
-
-# IDE-specific settings
-.idea/
-.vscode/
-
-# documentation
-site/
+library(testthat)
+library(sedonadb)
-# Python cache files
-__pycache__
+test_check("sedonadb")
diff --git a/.gitignore b/r/sedonadb/tests/testthat/test-adbc.R
similarity index 69%
copy from .gitignore
copy to r/sedonadb/tests/testthat/test-adbc.R
index 7441737..d4e6b83 100644
--- a/.gitignore
+++ b/r/sedonadb/tests/testthat/test-adbc.R
@@ -15,31 +15,17 @@
# specific language governing permissions and limitations
# under the License.
-# MacOS
-.DS_Store
-
-# sedona-cli
-.history
-
-# Generated by cargo
-debug/
-target/
-
-# Generated by rustfmt
-**/*.rs.bk
-
-# Generated by cargo llvm-cov
-coverage/
-
-# MSVC Windows builds of rustc generate these, which store debugging
information
-*.pdb
-
-# IDE-specific settings
-.idea/
-.vscode/
-
-# documentation
-site/
-
-# Python cache files
-__pycache__
+test_that("adbc driver works", {
+ con <- sedonadb_adbc() |>
+ adbcdrivermanager::adbc_database_init() |>
+ adbcdrivermanager::adbc_connection_init()
+
+ df <- con |>
+ adbcdrivermanager::read_adbc("SELECT ST_Point(0, 1) as geometry") |>
+ as.data.frame()
+
+ expect_identical(
+ wk::as_wkt(df$geometry),
+ wk::wkt("POINT (0 1)")
+ )
+})
diff --git a/.gitignore b/r/sedonadb/tests/testthat/test-context.R
similarity index 51%
copy from .gitignore
copy to r/sedonadb/tests/testthat/test-context.R
index 7441737..b6ea4e6 100644
--- a/.gitignore
+++ b/r/sedonadb/tests/testthat/test-context.R
@@ -15,31 +15,27 @@
# specific language governing permissions and limitations
# under the License.
-# MacOS
-.DS_Store
-
-# sedona-cli
-.history
-
-# Generated by cargo
-debug/
-target/
-
-# Generated by rustfmt
-**/*.rs.bk
-
-# Generated by cargo llvm-cov
-coverage/
-
-# MSVC Windows builds of rustc generate these, which store debugging
information
-*.pdb
-
-# IDE-specific settings
-.idea/
-.vscode/
-
-# documentation
-site/
-
-# Python cache files
-__pycache__
+test_that("sd_read_parquet() works", {
+ path <- system.file("files/natural-earth_cities_geo.parquet", package =
"sedonadb")
+ expect_identical(sd_count(sd_read_parquet(path)), 243)
+
+ expect_identical(sd_count(sd_read_parquet(c(path, path))), 243 * 2)
+})
+
+test_that("views can be created and dropped", {
+ df <- sd_sql("SELECT 1 as one")
+ expect_true(rlang::is_reference(sd_to_view(df, "foofy"), df))
+ expect_identical(
+ sd_sql("SELECT * FROM foofy") |> sd_collect(),
+ data.frame(one = 1)
+ )
+
+ expect_identical(
+ sd_view("foofy") |> sd_collect(),
+ data.frame(one = 1)
+ )
+
+ sd_drop_view("foofy")
+ expect_error(sd_sql("SELECT * FROM foofy"), "table '(.*?)' not found")
+ expect_error(sd_view("foofy"), "No table named 'foofy'")
+})
diff --git a/r/sedonadb/tests/testthat/test-dataframe.R
b/r/sedonadb/tests/testthat/test-dataframe.R
new file mode 100644
index 0000000..f530f65
--- /dev/null
+++ b/r/sedonadb/tests/testthat/test-dataframe.R
@@ -0,0 +1,137 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+test_that("dataframe can be created from data.frame", {
+ df <- as_sedonadb_dataframe(data.frame(one = 1, two = "two"))
+ expect_s3_class(df, "sedonadb_dataframe")
+ expect_identical(sd_collect(df), data.frame(one = 1, two = "two"))
+
+ # Ensure that geo columns with crs are handled
+ df <- as_sedonadb_dataframe(
+ data.frame(
+ geom = wk::as_wkb(wk::wkt("POINT (0 1)", crs = "EPSG:32620"))
+ )
+ )
+
+ re_df <- sd_collect(df)
+ expect_identical(
+ wk::as_wkt(re_df$geom),
+ wk::wkt("POINT (0 1)", crs = wk::wk_crs_projjson("EPSG:32620"))
+ )
+})
+
+test_that("dataframe can be created from nanoarrow objects", {
+ r_df <- data.frame(geom = wk::as_wkb("POINT (0 1)"))
+
+ array <- nanoarrow::as_nanoarrow_array(r_df)
+ df <- as_sedonadb_dataframe(array)
+ expect_s3_class(df, "sedonadb_dataframe")
+ expect_identical(sd_collect(df, ptype = r_df), r_df)
+
+ stream <- nanoarrow::as_nanoarrow_array_stream(r_df)
+ df <- as_sedonadb_dataframe(stream, lazy = TRUE)
+ expect_s3_class(df, "sedonadb_dataframe")
+ expect_identical(sd_collect(df, ptype = r_df), r_df)
+
+ stream <- nanoarrow::as_nanoarrow_array_stream(r_df)
+ df <- as_sedonadb_dataframe(stream, lazy = FALSE)
+ expect_s3_class(df, "sedonadb_dataframe")
+ expect_identical(sd_collect(df, ptype = r_df), r_df)
+})
+
+test_that("dataframe property accessors work", {
+ df <- sd_sql("SELECT ST_Point(0, 1) as pt")
+ expect_identical(ncol(df), 1L)
+ expect_identical(nrow(df), NA_integer_)
+ expect_identical(colnames(df), "pt")
+})
+
+test_that("dataframe head() works", {
+ df <- sd_sql("SELECT 1 as one, 'two' as two")
+ expect_identical(
+ as.data.frame(head(df, 0)),
+ data.frame(one = double(), two = character())
+ )
+})
+
+test_that("dataframe rows can be counted", {
+ df <- sd_sql("SELECT 1 as one, 'two' as two")
+ expect_identical(sd_count(df), 1)
+})
+
+test_that("dataframe can be computed", {
+ df <- sd_sql("SELECT 1 as one, 'two' as two")
+ df_computed <- sd_compute(df)
+ expect_identical(sd_collect(df), sd_collect(df_computed))
+})
+
+test_that("dataframe can be collected", {
+ df <- sd_sql("SELECT 1 as one, 'two' as two")
+ expect_identical(
+ sd_collect(df),
+ data.frame(one = 1, two = "two")
+ )
+
+ expect_identical(
+ sd_collect(df, ptype = data.frame(one = integer(), two = character())),
+ data.frame(one = 1L, two = "two")
+ )
+})
+
+test_that("dataframe can be converted to an R data.frame", {
+ df <- sd_sql("SELECT 1 as one, 'two' as two")
+ expect_identical(
+ as.data.frame(df),
+ data.frame(one = 1, two = "two")
+ )
+})
+
+test_that("dataframe can be converted to an array stream", {
+ df <- sd_sql("SELECT 1 as one, 'two' as two")
+ stream <- nanoarrow::as_nanoarrow_array_stream(df)
+ expect_s3_class(stream, "nanoarrow_array_stream")
+ expect_identical(
+ as.data.frame(stream),
+ data.frame(one = 1, two = "two")
+ )
+})
+
+test_that("dataframe can be printed", {
+ df <- sd_sql("SELECT ST_Point(0, 1) as pt")
+ expect_output(expect_identical(print(df), df), "POINT")
+})
+
+test_that("dataframe print uses ASCII when requested", {
+ df <- sd_sql("SELECT ST_Point(0, 1) as pt")
+ withr::with_options(list(cli.unicode = FALSE), {
+ expect_output(print(df), "--+")
+ })
+})
+
+test_that("dataframe print limits max output based on options", {
+ df <- sd_sql("SELECT ST_Point(0, 1) as pt")
+ withr::with_options(list(pillar.print_max = 0), {
+ expect_output(print(df), "Preview of up to 0 row\\(s\\)")
+ })
+})
+
+test_that("dataframe print limits max output based on options", {
+ df <- sd_sql("SELECT 'a really really really really long string' as str")
+ withr::with_options(list(width = 10, cli.unicode = FALSE), {
+ expect_output(print(df), "| a r... |")
+ })
+})