From 9bbb7dde14d1d1c646dba3495f988817db4a9583 Mon Sep 17 00:00:00 2001 From: Albert Xing Date: Tue, 16 Apr 2024 18:28:51 -0400 Subject: [PATCH 01/13] Update tpch benchmarking code --- sandbox/qe/src/bin/bench_tpch.rs | 17 ++++++++++------- sandbox/qe/tpch-individual.sh | 19 +++++++++++++------ 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/sandbox/qe/src/bin/bench_tpch.rs b/sandbox/qe/src/bin/bench_tpch.rs index 462183c4..3a22fcd8 100644 --- a/sandbox/qe/src/bin/bench_tpch.rs +++ b/sandbox/qe/src/bin/bench_tpch.rs @@ -85,22 +85,19 @@ async fn run_query_and_print_results( async fn run_tpch_queries(db: &DB, repetitions: u32) -> Result<(), DataFusionError> { let mut writer = csv::Writer::from_writer(io::stdout()); writer - .write_record(&["query", "avg_run_time_ms"]) + .write_record(&["query", "run_time_ms"]) .map_err(|e| DataFusionError::External(Box::new(e)))?; for q in 1..23 { let path = format!("./tpch/queries/{q}.sql"); let query = fs::read_to_string(path)?; - let mut total_time = 0; // Discard first run to allow for caching/initialization overhead run_timed_query(&db, &query).await?; for _ in 0..repetitions { let rt = run_timed_query(&db, &query).await?; - total_time += &rt.as_millis(); + writer + .write_record(&[&q.to_string(), &rt.as_millis().to_string()]) + .map_err(|e| DataFusionError::External(Box::new(e)))?; } - let avg_time = total_time / (repetitions as u128); - writer - .write_record(&[&q.to_string(), &avg_time.to_string()]) - .map_err(|e| DataFusionError::External(Box::new(e)))?; } writer.flush()?; Ok(()) @@ -240,6 +237,12 @@ async fn main() -> Result<(), DataFusionError> { eprintln!("\nNo modifications."); } } + Some(ref s) if s == "qs_explain" => { + let query = String::from(QUERY_SIMPLE); + let orig_physical_plan = db.to_physical_plan(&query).await?; + let dpp = displayable(orig_physical_plan.as_ref()); + eprintln!("\nOriginal plan\n{}", dpp.indent(false)); + } _ => (), } diff --git a/sandbox/qe/tpch-individual.sh b/sandbox/qe/tpch-individual.sh index 5b39f914..18aac0a6 100755 --- a/sandbox/qe/tpch-individual.sh +++ b/sandbox/qe/tpch-individual.sh @@ -8,16 +8,23 @@ NUMRUNS=10 +total_time=0 + if [ $# -gt 0 ]; then cd ~ sleep 10 docker exec -ti postgres psql -U postgres -d tpch -o /dev/null -c '\i /data/queries/'$1'.sql' | cat - start=$(date +%s.%N) # Capture the start time with nanoseconds as a decimal - for i in `seq 1 $NUMRUNS`;do docker exec -ti postgres psql -U postgres -d tpch -o /dev/null -c '\i /data/queries/'$1'.sql' | cat; done; - end=$(date +%s.%N) # Capture the end time with nanoseconds as a decimal - elapsed=$(awk "BEGIN{print $end - $start}") # Calculate the total elapsed time in seconds with awk - average=$(awk "BEGIN{printf \"%.3f\", ($elapsed / $NUMRUNS)}") # Calculate the average and format to 3 decimal places - echo "Average time to run query "$1": "$average" seconds" + for i in `seq 1 $NUMRUNS`; + do + start=$(date +%s.%N); # Capture the start time with nanoseconds as a decimal + docker exec -ti postgres psql -U postgres -d tpch -o /dev/null -c '\i /data/queries/'$1'.sql' | cat; + end=$(date +%s.%N); # Capture the end time with nanoseconds as a decimal + elapsed=$(awk "BEGIN{print $end - $start}"); # Calculate the total elapsed time in seconds with awk + total_time=$(awk "BEGIN{print $total_time + $elapsed}"); + echo "$1,$elapsed" + done; + average=$(awk "BEGIN{printf \"%.3f\", ($total_time / $NUMRUNS)}") # Calculate the average and format to 3 decimal places + # echo "Average time to run query "$1": "$average" seconds" fi From 036ed264358a5b93a00f09a9664db14aefbb2cb2 Mon Sep 17 00:00:00 2001 From: Albert Xing Date: Tue, 16 Apr 2024 18:29:40 -0400 Subject: [PATCH 02/13] add script to convert supplier key to binary file for RMI --- sandbox/qe/tpch/column_to_binary.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 sandbox/qe/tpch/column_to_binary.py diff --git a/sandbox/qe/tpch/column_to_binary.py b/sandbox/qe/tpch/column_to_binary.py new file mode 100644 index 00000000..ca2f8dfe --- /dev/null +++ b/sandbox/qe/tpch/column_to_binary.py @@ -0,0 +1,17 @@ +import csv +import struct + +def process_csv_to_binary(input_csv, output_bin, column_index): + with open(input_csv, 'r', newline='') as csvfile: + reader = csv.reader(csvfile, delimiter='|') + data = [int(row[column_index]) for row in reader] # Extract the column and convert to integers + + with open(output_bin, 'wb') as binfile: + # Write the number of items as a 64-bit unsigned integer + binfile.write(struct.pack(' Date: Wed, 17 Apr 2024 19:50:33 -0400 Subject: [PATCH 03/13] Update tpch scripts --- sandbox/qe/{tpch.sh => tpch-setup.sh} | 7 ++++++- sandbox/qe/tpch/headers.json | 16 ++++++++-------- sandbox/qe/tpch/tbl_to_csv.py | 4 +--- 3 files changed, 15 insertions(+), 12 deletions(-) rename sandbox/qe/{tpch.sh => tpch-setup.sh} (81%) diff --git a/sandbox/qe/tpch.sh b/sandbox/qe/tpch-setup.sh similarity index 81% rename from sandbox/qe/tpch.sh rename to sandbox/qe/tpch-setup.sh index c4e6f0d8..cbd027ab 100755 --- a/sandbox/qe/tpch.sh +++ b/sandbox/qe/tpch-setup.sh @@ -11,7 +11,12 @@ if [ $# -gt 0 ]; then cd ~/TPC-Hv3.0.1/dbgen ./dbgen -f -s $1 mv *.tbl /tmp/tpcdata - cd ~ + python3 ~/brad/sandbox/qe/tpch/tbl_to_csv.py /tmp/tpcdata + cd - + + # Uncomment if want to save generated info somewhere other than /tmp + # mkdir -p csvsf$1 + # cp /tmp/tpcdata/*.csv csvsf$1 docker stop postgres docker rm postgres diff --git a/sandbox/qe/tpch/headers.json b/sandbox/qe/tpch/headers.json index 86018387..ef427663 100644 --- a/sandbox/qe/tpch/headers.json +++ b/sandbox/qe/tpch/headers.json @@ -1,17 +1,17 @@ { -"part": "p_partkey, p_name, p_mfgr, p_brand, p_type, p_size, p_container, p_retailprice, p_comment, p_extra", +"part":"p_partkey,p_name,p_mfgr,p_brand,p_type,p_size,p_container,p_retailprice,p_comment", -"supplier": "s_suppkey, s_name, s_address, s_nationkey, s_phone, s_acctbal, s_comment, s_extra", +"supplier":"s_suppkey,s_name,s_address,s_nationkey,s_phone,s_acctbal,s_comment", -"partsupp": "ps_partkey, ps_suppkey, ps_availqty, ps_supplycost, ps_comment, ps_extra", +"partsupp":"ps_partkey,ps_suppkey,ps_availqty,ps_supplycost,ps_comment", -"customer": "c_custkey, c_name, c_address, c_nationkey, c_phone, c_acctbal, c_mktsegment, c_comment, c_extra", +"customer":"c_custkey,c_name,c_address,c_nationkey,c_phone,c_acctbal,c_mktsegment,c_comment", -"orders": "o_orderkey, o_custkey, o_orderstatus, o_totalprice, o_orderdate, o_orderpriority, o_clerk, o_shippriority, o_comment, o_extra", +"orders":"o_orderkey,o_custkey,o_orderstatus,o_totalprice,o_orderdate,o_orderpriority,o_clerk,o_shippriority,o_comment", -"lineitem": "l_orderkey, l_partkey, l_suppkey, l_linenumber, l_quantity, l_extendedprice, l_discount, l_tax, l_returnflag, l_linestatus, l_shipdate, l_commitdate, l_receiptdate, l_shipinstruct, l_shipmode, l_comment, l_extra", +"lineitem":"l_orderkey,l_partkey,l_suppkey,l_linenumber,l_quantity,l_extendedprice,l_discount,l_tax,l_returnflag,l_linestatus,l_shipdate,l_commitdate,l_receiptdate,l_shipinstruct,l_shipmode,l_comment", -"nation": "n_nationkey, n_name, n_regionkey, n_comment, n_extra", +"nation":"n_nationkey,n_name,n_regionkey,n_comment", -"region": "r_regionkey, r_name, r_comment, r_extra" +"region":"r_regionkey,r_name,r_comment" } \ No newline at end of file diff --git a/sandbox/qe/tpch/tbl_to_csv.py b/sandbox/qe/tpch/tbl_to_csv.py index ce7d6af0..3c2e8175 100644 --- a/sandbox/qe/tpch/tbl_to_csv.py +++ b/sandbox/qe/tpch/tbl_to_csv.py @@ -2,7 +2,6 @@ # df = pandas.read_table('sf1/customer.tbl', sep="|") # df.to_csv("sf1/customer.csv") -import os import json import sys @@ -13,8 +12,7 @@ def converttbldatatocsvformat(filename, header): tbl = open("".join([filename, ".tbl"]), "r") lines = tbl.readlines() for line in lines: - length = len(line) - line = line[:-2] + line[-1:] + line = line[:-2] + line[-1:] # remove trailing delimiter line = line.replace(",", "N") line = line.replace("|", ",") csv.write(line) From 81dcb11a74ae2ec069426e0d319e1618b9aaec5d Mon Sep 17 00:00:00 2001 From: Albert Xing Date: Thu, 2 May 2024 00:29:22 -0400 Subject: [PATCH 04/13] Create working bindings from RadixSpline repo to rust --- .gitignore | 4 + .gitmodules | 3 + sandbox/qe/Cargo.lock | 126 ++++++++++++++++++++++ sandbox/qe/Cargo.toml | 5 + sandbox/qe/RadixSpline | 1 + sandbox/qe/RadixSplineLib/radixspline.cpp | 26 +++++ sandbox/qe/RadixSplineLib/radixspline.hpp | 14 +++ sandbox/qe/build.rs | 85 +++++++++++++++ sandbox/qe/src/bin/test_radixspline.rs | 8 ++ 9 files changed, 272 insertions(+) create mode 100644 .gitmodules create mode 160000 sandbox/qe/RadixSpline create mode 100644 sandbox/qe/RadixSplineLib/radixspline.cpp create mode 100644 sandbox/qe/RadixSplineLib/radixspline.hpp create mode 100644 sandbox/qe/build.rs create mode 100644 sandbox/qe/src/bin/test_radixspline.rs diff --git a/.gitignore b/.gitignore index 2b56a765..d1370fed 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,7 @@ sandbox/qe/.brad_qe_repl_history # BRAD UI. ui/dist/ ui/node_modules/ + +*.tmp +*.o +*.a diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..1d0f7f1c --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "sandbox/qe/RadixSpline"] + path = sandbox/qe/RadixSpline + url = https://github.com/learnedsystems/RadixSpline diff --git a/sandbox/qe/Cargo.lock b/sandbox/qe/Cargo.lock index c3210ece..fe30d63a 100644 --- a/sandbox/qe/Cargo.lock +++ b/sandbox/qe/Cargo.lock @@ -407,6 +407,29 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "bindgen" +version = "0.69.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" +dependencies = [ + "bitflags 2.4.2", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.50", + "which", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -455,8 +478,10 @@ name = "brad_qe" version = "0.1.0" dependencies = [ "arrow", + "bindgen", "clap", "csv", + "cty", "datafusion", "futures", "rand", @@ -533,6 +558,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -573,6 +607,17 @@ dependencies = [ "phf_codegen", ] +[[package]] +name = "clang-sys" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.5.1" @@ -728,6 +773,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "cty" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" + [[package]] name = "dashmap" version = "5.5.3" @@ -1265,6 +1316,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "humantime" version = "2.1.0" @@ -1350,6 +1410,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "lexical-core" version = "0.8.5" @@ -1420,6 +1486,16 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "libloading" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +dependencies = [ + "cfg-if", + "windows-targets 0.52.0", +] + [[package]] name = "libm" version = "0.2.8" @@ -1495,6 +1571,12 @@ version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.2" @@ -1536,6 +1618,16 @@ dependencies = [ "libc", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "num" version = "0.4.1" @@ -1818,6 +1910,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "prettyplease" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3928fb5db768cb86f891ff014f0144589297e3c6a1aba6ed7cecfdace270c7" +dependencies = [ + "proc-macro2", + "syn 2.0.50", +] + [[package]] name = "proc-macro2" version = "1.0.78" @@ -1931,6 +2033,12 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.4.0" @@ -2057,6 +2165,12 @@ dependencies = [ "digest", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -2525,6 +2639,18 @@ version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/sandbox/qe/Cargo.toml b/sandbox/qe/Cargo.toml index 9b9650eb..c4ea2ec0 100644 --- a/sandbox/qe/Cargo.toml +++ b/sandbox/qe/Cargo.toml @@ -2,6 +2,7 @@ name = "brad_qe" version = "0.1.0" edition = "2021" +build = "build.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -14,6 +15,10 @@ arrow = "50.0.0" rustyline = "10.0.0" rand = { version = "0.8.5", features = ["small_rng"] } csv = "1.3.0" +cty = "0.2.2" + +[build-dependencies] +bindgen = "0.69.4" [[bin]] name = "brad_qe_repl" diff --git a/sandbox/qe/RadixSpline b/sandbox/qe/RadixSpline new file mode 160000 index 00000000..ab96aa59 --- /dev/null +++ b/sandbox/qe/RadixSpline @@ -0,0 +1 @@ +Subproject commit ab96aa59d429e7423beba2350bdcdf88952df282 diff --git a/sandbox/qe/RadixSplineLib/radixspline.cpp b/sandbox/qe/RadixSplineLib/radixspline.cpp new file mode 100644 index 00000000..5e3273d7 --- /dev/null +++ b/sandbox/qe/RadixSplineLib/radixspline.cpp @@ -0,0 +1,26 @@ +#include "radixspline.hpp" + +void* build(std::vector ks) { + RSData* rs = (RSData*)malloc(sizeof(RSData)); + rs->keys = ks; + uint64_t min = rs->keys.front(); + uint64_t max = rs->keys.back(); + rs::Builder rsb(min, max); + for (const auto& key : rs->keys) rsb.AddKey(key); + rs::RadixSpline rso = rsb.Finalize(); + memcpy(&(rs->rspline), &rso, sizeof(rs::RadixSpline)); + return (void*)rs; +} + +bool lookup(void* ptr, uint64_t key) { + RSData* rs = (RSData*) ptr; + rs::SearchBound bound = rs->rspline.GetSearchBound(key); + auto start = begin(rs->keys) + bound.begin, last = begin(rs->keys) + bound.end; + auto iter = std::lower_bound(start, last, key); + return iter != rs->keys.end() && *iter == key; +} + +void clear(void* ptr) { + RSData* rs = (RSData*) ptr; + free(rs); +} \ No newline at end of file diff --git a/sandbox/qe/RadixSplineLib/radixspline.hpp b/sandbox/qe/RadixSplineLib/radixspline.hpp new file mode 100644 index 00000000..d9dd4237 --- /dev/null +++ b/sandbox/qe/RadixSplineLib/radixspline.hpp @@ -0,0 +1,14 @@ +#include "../RadixSpline/include/rs/builder.h" +#include + + +struct RSData { + std::vector keys; + rs::RadixSpline rspline; +}; + +void* build(std::vector ks); + +bool lookup(void* ptr, uint64_t key); + +void clear(void* ptr); \ No newline at end of file diff --git a/sandbox/qe/build.rs b/sandbox/qe/build.rs new file mode 100644 index 00000000..de75c667 --- /dev/null +++ b/sandbox/qe/build.rs @@ -0,0 +1,85 @@ +use std::env; +use std::path::PathBuf; + +// use bindgen::CargoCallbacks; + +fn main() { + // This is the directory where the `c` library is located. + let libdir_path = PathBuf::from("RadixSplineLib") + // Canonicalize the path as `rustc-link-search` requires an absolute + // path. + .canonicalize() + .expect("cannot canonicalize path"); + + // This is the path to the `c` headers file. + let headers_path = libdir_path.join("radixspline.hpp"); + let headers_path_str = headers_path.to_str().expect("Path is not a valid string"); + + // This is the path to the intermediate object file for our library. + let obj_path = libdir_path.join("radixspline.o"); + // This is the path to the static library file. + let lib_path = libdir_path.join("libradixspline.a"); + + // Tell cargo to look for shared libraries in the specified directory + println!("cargo:rustc-link-search={}", libdir_path.to_str().unwrap()); + + // Tell cargo to tell rustc to link our `hello` library. Cargo will + // automatically know it must look for a `libhello.a` file. + println!("cargo:rustc-link-lib=radixspline"); + + // Run `clang` to compile the `hello.c` file into a `hello.o` object file. + // Unwrap if it is not possible to spawn the process. + if !std::process::Command::new("clang") + .arg("-c") + .arg("-o") + .arg(&obj_path) + .arg(libdir_path.join("radixspline.cpp")) + .output() + .expect("could not spawn `clang`") + .status + .success() + { + // Panic if the command was not successful. + panic!("could not compile object file"); + } + + // Run `ar` to generate the `libhello.a` file from the `hello.o` file. + // Unwrap if it is not possible to spawn the process. + if !std::process::Command::new("ar") + .arg("rcs") + .arg(lib_path) + .arg(obj_path) + .output() + .expect("could not spawn `ar`") + .status + .success() + { + // Panic if the command was not successful. + panic!("could not emit library file"); + } + + // The bindgen::Builder is the main entry point + // to bindgen, and lets you build up options for + // the resulting bindings. + let bindings = bindgen::Builder::default() + .opaque_type("^(std::.*)$") + .allowlist_function("build") + .allowlist_function("lookup") + .allowlist_function("clear") + // The input header we would like to generate + // bindings for. + .header(headers_path_str) + // Tell cargo to invalidate the built crate whenever any of the + // included header files changed. + .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) + // Finish the builder and generate the bindings. + .generate() + // Unwrap the Result and panic on failure. + .expect("Unable to generate bindings"); + + // Write the bindings to the $OUT_DIR/bindings.rs file. + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("bindings.rs"); + bindings + .write_to_file(out_path) + .expect("Couldn't write bindings!"); +} \ No newline at end of file diff --git a/sandbox/qe/src/bin/test_radixspline.rs b/sandbox/qe/src/bin/test_radixspline.rs new file mode 100644 index 00000000..45f215f9 --- /dev/null +++ b/sandbox/qe/src/bin/test_radixspline.rs @@ -0,0 +1,8 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] + +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); +fn main() -> () { + () +} From b655c92b7883bd0d27530be3e7e13c0191fa1c2c Mon Sep 17 00:00:00 2001 From: Albert Xing Date: Wed, 15 May 2024 02:03:13 -0400 Subject: [PATCH 05/13] Working radixspline implementation --- sandbox/qe/Cargo.lock | 16 +++++++++-- sandbox/qe/Cargo.toml | 9 ++---- sandbox/qe/RadixSplineLib/headers.hpp | 1 + sandbox/qe/RadixSplineLib/radixspline.cpp | 19 ++++++++++--- sandbox/qe/RadixSplineLib/radixspline.hpp | 10 +++++-- sandbox/qe/RadixSplineLib/radixsplino | Bin 0 -> 159600 bytes sandbox/qe/RadixSplineLib/run.cpp | 8 ++++++ sandbox/qe/RadixSplineLib/test.cpp | 8 ++++++ sandbox/qe/RadixSplineLib/test.hpp | 6 ++++ sandbox/qe/build.rs | 32 ++++++++++++++++------ sandbox/qe/src/bin/test_radixspline.rs | 23 ++++++++++++---- sandbox/qe/src/lib.rs | 8 ++++++ 12 files changed, 111 insertions(+), 29 deletions(-) create mode 100644 sandbox/qe/RadixSplineLib/headers.hpp create mode 100644 sandbox/qe/RadixSplineLib/radixsplino create mode 100644 sandbox/qe/RadixSplineLib/run.cpp create mode 100644 sandbox/qe/RadixSplineLib/test.cpp create mode 100644 sandbox/qe/RadixSplineLib/test.hpp diff --git a/sandbox/qe/Cargo.lock b/sandbox/qe/Cargo.lock index fe30d63a..01aea2eb 100644 --- a/sandbox/qe/Cargo.lock +++ b/sandbox/qe/Cargo.lock @@ -479,6 +479,7 @@ version = "0.1.0" dependencies = [ "arrow", "bindgen", + "cc", "clap", "csv", "cty", @@ -551,11 +552,13 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.86" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9fa1897e4325be0d68d48df6aa1a71ac2ed4d27723887e7754192705350730" +checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd" dependencies = [ + "jobserver", "libc", + "once_cell", ] [[package]] @@ -1395,6 +1398,15 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +[[package]] +name = "jobserver" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.68" diff --git a/sandbox/qe/Cargo.toml b/sandbox/qe/Cargo.toml index c4ea2ec0..1cd0072f 100644 --- a/sandbox/qe/Cargo.toml +++ b/sandbox/qe/Cargo.toml @@ -19,11 +19,8 @@ cty = "0.2.2" [build-dependencies] bindgen = "0.69.4" +cc = "1.0.96" [[bin]] -name = "brad_qe_repl" -path = "src/bin/repl.rs" - -[[bin]] -name = "bench_q3" -path = "src/bin/bench_q3.rs" +name = "test_radixspline" +path = "src/bin/test_radixspline.rs" \ No newline at end of file diff --git a/sandbox/qe/RadixSplineLib/headers.hpp b/sandbox/qe/RadixSplineLib/headers.hpp new file mode 100644 index 00000000..0e91c167 --- /dev/null +++ b/sandbox/qe/RadixSplineLib/headers.hpp @@ -0,0 +1 @@ +#include "radixspline.hpp" \ No newline at end of file diff --git a/sandbox/qe/RadixSplineLib/radixspline.cpp b/sandbox/qe/RadixSplineLib/radixspline.cpp index 5e3273d7..92d7c516 100644 --- a/sandbox/qe/RadixSplineLib/radixspline.cpp +++ b/sandbox/qe/RadixSplineLib/radixspline.cpp @@ -1,14 +1,25 @@ #include "radixspline.hpp" +#include -void* build(std::vector ks) { - RSData* rs = (RSData*)malloc(sizeof(RSData)); - rs->keys = ks; +int32_t add(int32_t a, int32_t b) { + return a + b; +} + +void* build(uint64_t* ks, uint64_t size) { + std::cout << "flag" << std::endl; + // RSData* rs = (RSData*)malloc(sizeof(RSData)); + RSData* rs = new RSData; + std::cout << "flag 2" << std::endl; + // rs->keys = std::vector(ks, ks + size); + rs->keys = std::vector(size); + memcpy(rs->keys.data(), ks, size * sizeof(uint64_t)); uint64_t min = rs->keys.front(); uint64_t max = rs->keys.back(); rs::Builder rsb(min, max); for (const auto& key : rs->keys) rsb.AddKey(key); rs::RadixSpline rso = rsb.Finalize(); - memcpy(&(rs->rspline), &rso, sizeof(rs::RadixSpline)); + // memcpy(&(rs->rspline), &rso, sizeof(rs::RadixSpline)); + rs->rspline = rso; return (void*)rs; } diff --git a/sandbox/qe/RadixSplineLib/radixspline.hpp b/sandbox/qe/RadixSplineLib/radixspline.hpp index d9dd4237..a94c627c 100644 --- a/sandbox/qe/RadixSplineLib/radixspline.hpp +++ b/sandbox/qe/RadixSplineLib/radixspline.hpp @@ -7,8 +7,12 @@ struct RSData { rs::RadixSpline rspline; }; -void* build(std::vector ks); +extern "C" { -bool lookup(void* ptr, uint64_t key); + int32_t add(int32_t a, int32_t b); + void* build(uint64_t* ks, uint64_t size); -void clear(void* ptr); \ No newline at end of file + bool lookup(void* ptr, uint64_t key); + + void clear(void* ptr); +} \ No newline at end of file diff --git a/sandbox/qe/RadixSplineLib/radixsplino b/sandbox/qe/RadixSplineLib/radixsplino new file mode 100644 index 0000000000000000000000000000000000000000..9c4165999c49900e16db2803881e7dd44a20b5e9 GIT binary patch literal 159600 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 G7yk>X0CpY# literal 0 HcmV?d00001 diff --git a/sandbox/qe/RadixSplineLib/run.cpp b/sandbox/qe/RadixSplineLib/run.cpp new file mode 100644 index 00000000..401d8eb5 --- /dev/null +++ b/sandbox/qe/RadixSplineLib/run.cpp @@ -0,0 +1,8 @@ +// test.cpp +#include "test.hpp" + +int main() { + add(10, 15); + return 0; +} + diff --git a/sandbox/qe/RadixSplineLib/test.cpp b/sandbox/qe/RadixSplineLib/test.cpp new file mode 100644 index 00000000..e2f560f4 --- /dev/null +++ b/sandbox/qe/RadixSplineLib/test.cpp @@ -0,0 +1,8 @@ +#include "test.hpp" +int32_t add(int32_t a, int32_t b) { + return a + b; +} + +int32_t multiply(int32_t a, int32_t b) { + return a * b; +} \ No newline at end of file diff --git a/sandbox/qe/RadixSplineLib/test.hpp b/sandbox/qe/RadixSplineLib/test.hpp new file mode 100644 index 00000000..e5ca401f --- /dev/null +++ b/sandbox/qe/RadixSplineLib/test.hpp @@ -0,0 +1,6 @@ +#include + +extern "C" { +int32_t add(int32_t a, int32_t b); +int32_t multiply(int32_t a, int32_t b); +} \ No newline at end of file diff --git a/sandbox/qe/build.rs b/sandbox/qe/build.rs index de75c667..a3a9f630 100644 --- a/sandbox/qe/build.rs +++ b/sandbox/qe/build.rs @@ -20,16 +20,9 @@ fn main() { // This is the path to the static library file. let lib_path = libdir_path.join("libradixspline.a"); - // Tell cargo to look for shared libraries in the specified directory - println!("cargo:rustc-link-search={}", libdir_path.to_str().unwrap()); - - // Tell cargo to tell rustc to link our `hello` library. Cargo will - // automatically know it must look for a `libhello.a` file. - println!("cargo:rustc-link-lib=radixspline"); - // Run `clang` to compile the `hello.c` file into a `hello.o` object file. // Unwrap if it is not possible to spawn the process. - if !std::process::Command::new("clang") + if !std::process::Command::new("clang++") .arg("-c") .arg("-o") .arg(&obj_path) @@ -46,7 +39,7 @@ fn main() { // Run `ar` to generate the `libhello.a` file from the `hello.o` file. // Unwrap if it is not possible to spawn the process. if !std::process::Command::new("ar") - .arg("rcs") + .arg("rcus") .arg(lib_path) .arg(obj_path) .output() @@ -58,6 +51,24 @@ fn main() { panic!("could not emit library file"); } + // Build::new() + // .file(headers_path_str) + // .include(libdir_path) + // .compile("radixspline"); + + // Build::new() + // .file(libdir_path.join("radixspline.cpp")) + // .cpp(true) // Indicates that this is C++ code + // .compile(libdir_path.join("libradixspline.a").to_str().unwrap()); + + // Tell cargo to look for shared libraries in the specified directory + println!("cargo:rustc-link-search=native={}", libdir_path.to_str().unwrap()); + + // Tell cargo to tell rustc to link our `hello` library. Cargo will + // automatically know it must look for a `libhello.a` file. + println!("cargo:rustc-link-lib=radixspline"); + println!("cargo:rustc-link-lib=stdc++"); + // The bindgen::Builder is the main entry point // to bindgen, and lets you build up options for // the resulting bindings. @@ -66,6 +77,9 @@ fn main() { .allowlist_function("build") .allowlist_function("lookup") .allowlist_function("clear") + // .allowlist_file("^(.*radixspline.hpp)$") + .allowlist_function("add") + // .allowlist_function("multiply") // The input header we would like to generate // bindings for. .header(headers_path_str) diff --git a/sandbox/qe/src/bin/test_radixspline.rs b/sandbox/qe/src/bin/test_radixspline.rs index 45f215f9..93f2f662 100644 --- a/sandbox/qe/src/bin/test_radixspline.rs +++ b/sandbox/qe/src/bin/test_radixspline.rs @@ -1,8 +1,21 @@ -#![allow(non_upper_case_globals)] -#![allow(non_camel_case_types)] -#![allow(non_snake_case)] +pub use brad_qe::radixspline::build; +pub use brad_qe::radixspline::lookup; +pub use brad_qe::radixspline::clear; +pub use brad_qe::radixspline::add; + -include!(concat!(env!("OUT_DIR"), "/bindings.rs")); fn main() -> () { - () + let mut input: [u64; 15] = [1,2,3,4,6,7,8,9,11,12,13,15,17,20,21]; + unsafe { + println!("asdfasfsda {}", add(10,15)); + let rs_ptr = build(input.as_mut_ptr(), 15); + println!("asdfasfsda"); + println!("7 is in the array: {}", lookup(rs_ptr, 7)); + + println!("13 is in the array: {}", lookup(rs_ptr, 13)); + + println!("5 is in the array: {}", lookup(rs_ptr, 5)); + // println!("{}", add(15, 20)); + // println!("{}", abs(-5)); + } } diff --git a/sandbox/qe/src/lib.rs b/sandbox/qe/src/lib.rs index 564c28ca..30c44e6e 100644 --- a/sandbox/qe/src/lib.rs +++ b/sandbox/qe/src/lib.rs @@ -23,6 +23,14 @@ pub mod ops; /// Utilities for rewriting DataFusion `ExecutionPlan`s. pub mod rewrite; +pub mod radixspline { + #![allow(non_upper_case_globals)] + #![allow(non_camel_case_types)] + #![allow(non_snake_case)] + + include!(concat!(env!("OUT_DIR"), "/bindings.rs")); +} + /// Represents an "open" IOHTAP database. Eventually, the DB should run as a /// daemon process. For now it is just an embedded DB (similar to SQLite). pub struct DB { From 9f5dba77a4e0630c267f342e1dcaace797e8a3a0 Mon Sep 17 00:00:00 2001 From: Albert Xing Date: Wed, 15 May 2024 02:19:59 -0400 Subject: [PATCH 06/13] Clean up radixspline c++ library --- sandbox/qe/RadixSplineLib/headers.hpp | 1 - sandbox/qe/RadixSplineLib/radixspline.cpp | 10 ---------- sandbox/qe/RadixSplineLib/radixsplino | Bin 159600 -> 0 bytes sandbox/qe/RadixSplineLib/run.cpp | 8 -------- sandbox/qe/RadixSplineLib/test.cpp | 8 -------- sandbox/qe/RadixSplineLib/test.hpp | 6 ------ sandbox/qe/build.rs | 10 ---------- sandbox/qe/src/bin/test_radixspline.rs | 5 ----- 8 files changed, 48 deletions(-) delete mode 100644 sandbox/qe/RadixSplineLib/headers.hpp delete mode 100644 sandbox/qe/RadixSplineLib/radixsplino delete mode 100644 sandbox/qe/RadixSplineLib/run.cpp delete mode 100644 sandbox/qe/RadixSplineLib/test.cpp delete mode 100644 sandbox/qe/RadixSplineLib/test.hpp diff --git a/sandbox/qe/RadixSplineLib/headers.hpp b/sandbox/qe/RadixSplineLib/headers.hpp deleted file mode 100644 index 0e91c167..00000000 --- a/sandbox/qe/RadixSplineLib/headers.hpp +++ /dev/null @@ -1 +0,0 @@ -#include "radixspline.hpp" \ No newline at end of file diff --git a/sandbox/qe/RadixSplineLib/radixspline.cpp b/sandbox/qe/RadixSplineLib/radixspline.cpp index 92d7c516..612a87e6 100644 --- a/sandbox/qe/RadixSplineLib/radixspline.cpp +++ b/sandbox/qe/RadixSplineLib/radixspline.cpp @@ -1,16 +1,7 @@ #include "radixspline.hpp" -#include - -int32_t add(int32_t a, int32_t b) { - return a + b; -} void* build(uint64_t* ks, uint64_t size) { - std::cout << "flag" << std::endl; - // RSData* rs = (RSData*)malloc(sizeof(RSData)); RSData* rs = new RSData; - std::cout << "flag 2" << std::endl; - // rs->keys = std::vector(ks, ks + size); rs->keys = std::vector(size); memcpy(rs->keys.data(), ks, size * sizeof(uint64_t)); uint64_t min = rs->keys.front(); @@ -18,7 +9,6 @@ void* build(uint64_t* ks, uint64_t size) { rs::Builder rsb(min, max); for (const auto& key : rs->keys) rsb.AddKey(key); rs::RadixSpline rso = rsb.Finalize(); - // memcpy(&(rs->rspline), &rso, sizeof(rs::RadixSpline)); rs->rspline = rso; return (void*)rs; } diff --git a/sandbox/qe/RadixSplineLib/radixsplino b/sandbox/qe/RadixSplineLib/radixsplino deleted file mode 100644 index 9c4165999c49900e16db2803881e7dd44a20b5e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 159600 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 G7yk>X0CpY# diff --git a/sandbox/qe/RadixSplineLib/run.cpp b/sandbox/qe/RadixSplineLib/run.cpp deleted file mode 100644 index 401d8eb5..00000000 --- a/sandbox/qe/RadixSplineLib/run.cpp +++ /dev/null @@ -1,8 +0,0 @@ -// test.cpp -#include "test.hpp" - -int main() { - add(10, 15); - return 0; -} - diff --git a/sandbox/qe/RadixSplineLib/test.cpp b/sandbox/qe/RadixSplineLib/test.cpp deleted file mode 100644 index e2f560f4..00000000 --- a/sandbox/qe/RadixSplineLib/test.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "test.hpp" -int32_t add(int32_t a, int32_t b) { - return a + b; -} - -int32_t multiply(int32_t a, int32_t b) { - return a * b; -} \ No newline at end of file diff --git a/sandbox/qe/RadixSplineLib/test.hpp b/sandbox/qe/RadixSplineLib/test.hpp deleted file mode 100644 index e5ca401f..00000000 --- a/sandbox/qe/RadixSplineLib/test.hpp +++ /dev/null @@ -1,6 +0,0 @@ -#include - -extern "C" { -int32_t add(int32_t a, int32_t b); -int32_t multiply(int32_t a, int32_t b); -} \ No newline at end of file diff --git a/sandbox/qe/build.rs b/sandbox/qe/build.rs index a3a9f630..9c9a2c43 100644 --- a/sandbox/qe/build.rs +++ b/sandbox/qe/build.rs @@ -51,16 +51,6 @@ fn main() { panic!("could not emit library file"); } - // Build::new() - // .file(headers_path_str) - // .include(libdir_path) - // .compile("radixspline"); - - // Build::new() - // .file(libdir_path.join("radixspline.cpp")) - // .cpp(true) // Indicates that this is C++ code - // .compile(libdir_path.join("libradixspline.a").to_str().unwrap()); - // Tell cargo to look for shared libraries in the specified directory println!("cargo:rustc-link-search=native={}", libdir_path.to_str().unwrap()); diff --git a/sandbox/qe/src/bin/test_radixspline.rs b/sandbox/qe/src/bin/test_radixspline.rs index 93f2f662..e4c2f5f1 100644 --- a/sandbox/qe/src/bin/test_radixspline.rs +++ b/sandbox/qe/src/bin/test_radixspline.rs @@ -1,21 +1,16 @@ pub use brad_qe::radixspline::build; pub use brad_qe::radixspline::lookup; pub use brad_qe::radixspline::clear; -pub use brad_qe::radixspline::add; fn main() -> () { let mut input: [u64; 15] = [1,2,3,4,6,7,8,9,11,12,13,15,17,20,21]; unsafe { - println!("asdfasfsda {}", add(10,15)); let rs_ptr = build(input.as_mut_ptr(), 15); - println!("asdfasfsda"); println!("7 is in the array: {}", lookup(rs_ptr, 7)); println!("13 is in the array: {}", lookup(rs_ptr, 13)); println!("5 is in the array: {}", lookup(rs_ptr, 5)); - // println!("{}", add(15, 20)); - // println!("{}", abs(-5)); } } From 1ceaf3b0f3eb2d67a02610655b9471dd83c8ba76 Mon Sep 17 00:00:00 2001 From: Albert Xing Date: Wed, 15 May 2024 03:07:10 -0400 Subject: [PATCH 07/13] Implement safe wrapper library around radixspline --- sandbox/qe/RadixSplineLib/radixspline.cpp | 2 +- sandbox/qe/RadixSplineLib/radixspline.hpp | 2 +- sandbox/qe/src/bin/test_radixspline.rs | 28 +++++++----- sandbox/qe/src/lib.rs | 8 +--- sandbox/qe/src/radixspline.rs | 54 +++++++++++++++++++++++ 5 files changed, 75 insertions(+), 19 deletions(-) create mode 100644 sandbox/qe/src/radixspline.rs diff --git a/sandbox/qe/RadixSplineLib/radixspline.cpp b/sandbox/qe/RadixSplineLib/radixspline.cpp index 612a87e6..afa1e700 100644 --- a/sandbox/qe/RadixSplineLib/radixspline.cpp +++ b/sandbox/qe/RadixSplineLib/radixspline.cpp @@ -1,6 +1,6 @@ #include "radixspline.hpp" -void* build(uint64_t* ks, uint64_t size) { +void* build(const uint64_t* ks, uint64_t size) { RSData* rs = new RSData; rs->keys = std::vector(size); memcpy(rs->keys.data(), ks, size * sizeof(uint64_t)); diff --git a/sandbox/qe/RadixSplineLib/radixspline.hpp b/sandbox/qe/RadixSplineLib/radixspline.hpp index a94c627c..0c1e0bff 100644 --- a/sandbox/qe/RadixSplineLib/radixspline.hpp +++ b/sandbox/qe/RadixSplineLib/radixspline.hpp @@ -10,7 +10,7 @@ struct RSData { extern "C" { int32_t add(int32_t a, int32_t b); - void* build(uint64_t* ks, uint64_t size); + void* build(const uint64_t* ks, uint64_t size); bool lookup(void* ptr, uint64_t key); diff --git a/sandbox/qe/src/bin/test_radixspline.rs b/sandbox/qe/src/bin/test_radixspline.rs index e4c2f5f1..5aecab46 100644 --- a/sandbox/qe/src/bin/test_radixspline.rs +++ b/sandbox/qe/src/bin/test_radixspline.rs @@ -1,16 +1,24 @@ -pub use brad_qe::radixspline::build; -pub use brad_qe::radixspline::lookup; -pub use brad_qe::radixspline::clear; +// pub use brad_qe::radixspline::build; +// pub use brad_qe::radixspline::lookup; +// pub use brad_qe::radixspline::clear; +use brad_qe::radixspline::RadixSpline; fn main() -> () { - let mut input: [u64; 15] = [1,2,3,4,6,7,8,9,11,12,13,15,17,20,21]; - unsafe { - let rs_ptr = build(input.as_mut_ptr(), 15); - println!("7 is in the array: {}", lookup(rs_ptr, 7)); + let input: [u64; 15] = [1,2,3,4,6,7,8,9,11,12,13,15,17,20,21]; + // unsafe { + // let rs_ptr = build(input.as_ptr(), 15); + // println!("7 is in the array: {}", lookup(rs_ptr, 7)); - println!("13 is in the array: {}", lookup(rs_ptr, 13)); + // println!("13 is in the array: {}", lookup(rs_ptr, 13)); + + // println!("5 is in the array: {}", lookup(rs_ptr, 5)); + // } + let rspline = RadixSpline::build_simple(input); + println!("7 is in the array: {}", rspline.lookup(7)); + + println!("13 is in the array: {}", rspline.lookup(13)); + + println!("5 is in the array: {}", rspline.lookup(5)); - println!("5 is in the array: {}", lookup(rs_ptr, 5)); - } } diff --git a/sandbox/qe/src/lib.rs b/sandbox/qe/src/lib.rs index 30c44e6e..8af980bf 100644 --- a/sandbox/qe/src/lib.rs +++ b/sandbox/qe/src/lib.rs @@ -23,13 +23,7 @@ pub mod ops; /// Utilities for rewriting DataFusion `ExecutionPlan`s. pub mod rewrite; -pub mod radixspline { - #![allow(non_upper_case_globals)] - #![allow(non_camel_case_types)] - #![allow(non_snake_case)] - - include!(concat!(env!("OUT_DIR"), "/bindings.rs")); -} +pub mod radixspline; /// Represents an "open" IOHTAP database. Eventually, the DB should run as a /// daemon process. For now it is just an embedded DB (similar to SQLite). diff --git a/sandbox/qe/src/radixspline.rs b/sandbox/qe/src/radixspline.rs new file mode 100644 index 00000000..e07dde32 --- /dev/null +++ b/sandbox/qe/src/radixspline.rs @@ -0,0 +1,54 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] + +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); + +use arrow::{array::{UInt64Array, Array}, record_batch::RecordBatch}; +use std::os::raw::c_void; + +// pub use build; +// pub use lookup; +// pub use clear; + +pub struct RadixSpline { + rs_ptr: *mut c_void, +} + +impl RadixSpline { + + pub fn build_simple(data: [u64; 15]) -> RadixSpline { + unsafe { + let rs_ptr = build(data.as_ptr(), 15); + RadixSpline { + rs_ptr, + } + } + } + + pub fn build(record_batch: &RecordBatch, column_index: usize) -> RadixSpline { + let column = record_batch.column(column_index); + let u64_array = column.as_any().downcast_ref::().unwrap(); + + let ptr = u64_array.values().as_ptr(); + let size = column.len() as u64; + unsafe { + let rs_ptr = build(ptr, size); + RadixSpline { + rs_ptr, + } + } + } + + pub fn lookup(&self, key: u64) -> bool { + unsafe { + lookup(self.rs_ptr, key) + } + } + + pub fn clear(&self) { + unsafe { + clear(self.rs_ptr) + } + } +} \ No newline at end of file From b70aead5c9df46191b98fb1f93f9a674e1d83619 Mon Sep 17 00:00:00 2001 From: Albert Xing Date: Wed, 15 May 2024 03:07:54 -0400 Subject: [PATCH 08/13] Clean up code --- sandbox/qe/src/bin/test_radixspline.rs | 12 ------------ sandbox/qe/src/radixspline.rs | 4 ---- 2 files changed, 16 deletions(-) diff --git a/sandbox/qe/src/bin/test_radixspline.rs b/sandbox/qe/src/bin/test_radixspline.rs index 5aecab46..33a406ec 100644 --- a/sandbox/qe/src/bin/test_radixspline.rs +++ b/sandbox/qe/src/bin/test_radixspline.rs @@ -1,19 +1,7 @@ -// pub use brad_qe::radixspline::build; -// pub use brad_qe::radixspline::lookup; -// pub use brad_qe::radixspline::clear; - use brad_qe::radixspline::RadixSpline; fn main() -> () { let input: [u64; 15] = [1,2,3,4,6,7,8,9,11,12,13,15,17,20,21]; - // unsafe { - // let rs_ptr = build(input.as_ptr(), 15); - // println!("7 is in the array: {}", lookup(rs_ptr, 7)); - - // println!("13 is in the array: {}", lookup(rs_ptr, 13)); - - // println!("5 is in the array: {}", lookup(rs_ptr, 5)); - // } let rspline = RadixSpline::build_simple(input); println!("7 is in the array: {}", rspline.lookup(7)); diff --git a/sandbox/qe/src/radixspline.rs b/sandbox/qe/src/radixspline.rs index e07dde32..82accc40 100644 --- a/sandbox/qe/src/radixspline.rs +++ b/sandbox/qe/src/radixspline.rs @@ -7,10 +7,6 @@ include!(concat!(env!("OUT_DIR"), "/bindings.rs")); use arrow::{array::{UInt64Array, Array}, record_batch::RecordBatch}; use std::os::raw::c_void; -// pub use build; -// pub use lookup; -// pub use clear; - pub struct RadixSpline { rs_ptr: *mut c_void, } From fc4b3ad4241f26797e1ba5149e51a56c01a70f88 Mon Sep 17 00:00:00 2001 From: Albert Xing Date: Tue, 4 Jun 2024 05:34:34 -0400 Subject: [PATCH 09/13] Clean up code and address comments --- sandbox/qe/Cargo.toml | 4 +++- sandbox/qe/RadixSplineLib/radixspline.cpp | 4 ++-- .../{radixspline.hpp => radixspline.h} | 2 +- sandbox/qe/build.rs | 17 ++++++----------- sandbox/qe/src/radixspline.rs | 2 +- sandbox/qe/tpch-every.sh | 3 +-- sandbox/qe/tpch-individual.sh | 9 ++------- sandbox/qe/tpch-setup.sh | 7 +++---- sandbox/qe/tpch/headers.json | 2 +- sandbox/qe/tpch/tbl_to_csv.py | 4 ++-- 10 files changed, 22 insertions(+), 32 deletions(-) rename sandbox/qe/RadixSplineLib/{radixspline.hpp => radixspline.h} (99%) diff --git a/sandbox/qe/Cargo.toml b/sandbox/qe/Cargo.toml index 1cd0072f..f068f020 100644 --- a/sandbox/qe/Cargo.toml +++ b/sandbox/qe/Cargo.toml @@ -22,5 +22,7 @@ bindgen = "0.69.4" cc = "1.0.96" [[bin]] +name = "bench_q3" +path = "src/bin/bench_q3.rs" name = "test_radixspline" -path = "src/bin/test_radixspline.rs" \ No newline at end of file +path = "src/bin/test_radixspline.rs" diff --git a/sandbox/qe/RadixSplineLib/radixspline.cpp b/sandbox/qe/RadixSplineLib/radixspline.cpp index afa1e700..8603404b 100644 --- a/sandbox/qe/RadixSplineLib/radixspline.cpp +++ b/sandbox/qe/RadixSplineLib/radixspline.cpp @@ -23,5 +23,5 @@ bool lookup(void* ptr, uint64_t key) { void clear(void* ptr) { RSData* rs = (RSData*) ptr; - free(rs); -} \ No newline at end of file + delete rs; +} diff --git a/sandbox/qe/RadixSplineLib/radixspline.hpp b/sandbox/qe/RadixSplineLib/radixspline.h similarity index 99% rename from sandbox/qe/RadixSplineLib/radixspline.hpp rename to sandbox/qe/RadixSplineLib/radixspline.h index 0c1e0bff..e5b6b6c9 100644 --- a/sandbox/qe/RadixSplineLib/radixspline.hpp +++ b/sandbox/qe/RadixSplineLib/radixspline.h @@ -15,4 +15,4 @@ extern "C" { bool lookup(void* ptr, uint64_t key); void clear(void* ptr); -} \ No newline at end of file +} diff --git a/sandbox/qe/build.rs b/sandbox/qe/build.rs index 9c9a2c43..482318cf 100644 --- a/sandbox/qe/build.rs +++ b/sandbox/qe/build.rs @@ -1,8 +1,6 @@ use std::env; use std::path::PathBuf; -// use bindgen::CargoCallbacks; - fn main() { // This is the directory where the `c` library is located. let libdir_path = PathBuf::from("RadixSplineLib") @@ -12,7 +10,7 @@ fn main() { .expect("cannot canonicalize path"); // This is the path to the `c` headers file. - let headers_path = libdir_path.join("radixspline.hpp"); + let headers_path = libdir_path.join("radixspline.h"); let headers_path_str = headers_path.to_str().expect("Path is not a valid string"); // This is the path to the intermediate object file for our library. @@ -20,7 +18,7 @@ fn main() { // This is the path to the static library file. let lib_path = libdir_path.join("libradixspline.a"); - // Run `clang` to compile the `hello.c` file into a `hello.o` object file. + // Run `clang` to compile the `radixspline.cpp` file into a `radixspline.o` object file. // Unwrap if it is not possible to spawn the process. if !std::process::Command::new("clang++") .arg("-c") @@ -36,7 +34,7 @@ fn main() { panic!("could not compile object file"); } - // Run `ar` to generate the `libhello.a` file from the `hello.o` file. + // Run `ar` to generate the `libradixspline.a` file from the `radixspline.o` file. // Unwrap if it is not possible to spawn the process. if !std::process::Command::new("ar") .arg("rcus") @@ -54,8 +52,8 @@ fn main() { // Tell cargo to look for shared libraries in the specified directory println!("cargo:rustc-link-search=native={}", libdir_path.to_str().unwrap()); - // Tell cargo to tell rustc to link our `hello` library. Cargo will - // automatically know it must look for a `libhello.a` file. + // Tell cargo to tell rustc to link our `radixspline` library. Cargo will + // automatically know it must look for a `libradixspline.a` file. println!("cargo:rustc-link-lib=radixspline"); println!("cargo:rustc-link-lib=stdc++"); @@ -67,9 +65,6 @@ fn main() { .allowlist_function("build") .allowlist_function("lookup") .allowlist_function("clear") - // .allowlist_file("^(.*radixspline.hpp)$") - .allowlist_function("add") - // .allowlist_function("multiply") // The input header we would like to generate // bindings for. .header(headers_path_str) @@ -86,4 +81,4 @@ fn main() { bindings .write_to_file(out_path) .expect("Couldn't write bindings!"); -} \ No newline at end of file +} diff --git a/sandbox/qe/src/radixspline.rs b/sandbox/qe/src/radixspline.rs index 82accc40..dd3f4440 100644 --- a/sandbox/qe/src/radixspline.rs +++ b/sandbox/qe/src/radixspline.rs @@ -47,4 +47,4 @@ impl RadixSpline { clear(self.rs_ptr) } } -} \ No newline at end of file +} diff --git a/sandbox/qe/tpch-every.sh b/sandbox/qe/tpch-every.sh index e65fb254..fbb43a6b 100755 --- a/sandbox/qe/tpch-every.sh +++ b/sandbox/qe/tpch-every.sh @@ -4,7 +4,6 @@ cd ~/brad/sandbox/qe for q in `seq 1 22`; do echo "timing query $q"; - timeout --foreground 2m ./tpch-individual.sh $q + timeout --foreground 5m ./tpch-individual.sh $q # timeout --foreground 30s ./tpch-individual.sh 2; done; - diff --git a/sandbox/qe/tpch-individual.sh b/sandbox/qe/tpch-individual.sh index 18aac0a6..57d00021 100755 --- a/sandbox/qe/tpch-individual.sh +++ b/sandbox/qe/tpch-individual.sh @@ -6,7 +6,7 @@ # mv *.sql ~/brad/sandbox/qe/queries/ # cd ~ -NUMRUNS=10 +NUMRUNS=3 total_time=0 @@ -17,7 +17,7 @@ if [ $# -gt 0 ]; then for i in `seq 1 $NUMRUNS`; do start=$(date +%s.%N); # Capture the start time with nanoseconds as a decimal - docker exec -ti postgres psql -U postgres -d tpch -o /dev/null -c '\i /data/queries/'$1'.sql' | cat; + docker exec postgres psql -U postgres -d tpch -o /dev/null -c '\i /data/queries/'$1'.sql' | cat; end=$(date +%s.%N); # Capture the end time with nanoseconds as a decimal elapsed=$(awk "BEGIN{print $end - $start}"); # Calculate the total elapsed time in seconds with awk total_time=$(awk "BEGIN{print $total_time + $elapsed}"); @@ -26,8 +26,3 @@ if [ $# -gt 0 ]; then average=$(awk "BEGIN{printf \"%.3f\", ($total_time / $NUMRUNS)}") # Calculate the average and format to 3 decimal places # echo "Average time to run query "$1": "$average" seconds" fi - - - - - diff --git a/sandbox/qe/tpch-setup.sh b/sandbox/qe/tpch-setup.sh index cbd027ab..c87f8186 100755 --- a/sandbox/qe/tpch-setup.sh +++ b/sandbox/qe/tpch-setup.sh @@ -9,9 +9,9 @@ if [ $# -gt 0 ]; then # generate data for specified Scale Factor cd ~/TPC-Hv3.0.1/dbgen - ./dbgen -f -s $1 - mv *.tbl /tmp/tpcdata - python3 ~/brad/sandbox/qe/tpch/tbl_to_csv.py /tmp/tpcdata + # ./dbgen -f -s $1 + # mv *.tbl /tmp/tpcdata + # python3 ~/brad/sandbox/qe/tpch/tbl_to_csv.py /tmp/tpcdata cd - # Uncomment if want to save generated info somewhere other than /tmp @@ -32,4 +32,3 @@ fi # time { # for q in `seq 1 22`;do docker exec -ti postgres psql -U postgres -d tpch -o /dev/null -c '\i /data/queries/'$q'.sql' | cat; done; # } - diff --git a/sandbox/qe/tpch/headers.json b/sandbox/qe/tpch/headers.json index ef427663..6dfc2732 100644 --- a/sandbox/qe/tpch/headers.json +++ b/sandbox/qe/tpch/headers.json @@ -14,4 +14,4 @@ "nation":"n_nationkey,n_name,n_regionkey,n_comment", "region":"r_regionkey,r_name,r_comment" -} \ No newline at end of file +} diff --git a/sandbox/qe/tpch/tbl_to_csv.py b/sandbox/qe/tpch/tbl_to_csv.py index 3c2e8175..ae7b84bf 100644 --- a/sandbox/qe/tpch/tbl_to_csv.py +++ b/sandbox/qe/tpch/tbl_to_csv.py @@ -19,8 +19,8 @@ def converttbldatatocsvformat(filename, header): tbl.close() csv.close() - -json_file = "/home/axing/brad/sandbox/qe/tpch/headers.json" +# TODO: Need to change this path later +json_file = '/home/axing/brad/sandbox/qe/tpch/headers.json' if __name__ == "__main__": # Load the schema definitions from the YAML file with open(json_file, "r") as f: From f406ffb93a20bb4bd928bee37f340b0f4c4028d5 Mon Sep 17 00:00:00 2001 From: Albert Xing Date: Tue, 4 Jun 2024 05:36:57 -0400 Subject: [PATCH 10/13] Black format python files --- sandbox/qe/tpch/column_to_binary.py | 18 +++++++++++------- sandbox/qe/tpch/tbl_to_csv.py | 3 ++- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/sandbox/qe/tpch/column_to_binary.py b/sandbox/qe/tpch/column_to_binary.py index ca2f8dfe..6006e97a 100644 --- a/sandbox/qe/tpch/column_to_binary.py +++ b/sandbox/qe/tpch/column_to_binary.py @@ -1,17 +1,21 @@ import csv import struct + def process_csv_to_binary(input_csv, output_bin, column_index): - with open(input_csv, 'r', newline='') as csvfile: - reader = csv.reader(csvfile, delimiter='|') - data = [int(row[column_index]) for row in reader] # Extract the column and convert to integers + with open(input_csv, "r", newline="") as csvfile: + reader = csv.reader(csvfile, delimiter="|") + data = [ + int(row[column_index]) for row in reader + ] # Extract the column and convert to integers - with open(output_bin, 'wb') as binfile: + with open(output_bin, "wb") as binfile: # Write the number of items as a 64-bit unsigned integer - binfile.write(struct.pack(' Date: Tue, 4 Jun 2024 05:47:18 -0400 Subject: [PATCH 11/13] Add script for testing tpch via redshift --- sandbox/qe/redshift.py | 252 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 sandbox/qe/redshift.py diff --git a/sandbox/qe/redshift.py b/sandbox/qe/redshift.py new file mode 100644 index 00000000..b0f106ab --- /dev/null +++ b/sandbox/qe/redshift.py @@ -0,0 +1,252 @@ +import argparse +import os +import redshift_connector +import time + + +def init(cs): + cs.execute( + """ + CREATE TABLE IF NOT EXISTS "nation" ( + "n_nationkey" INT, + "n_name" CHAR(25), + "n_regionkey" INT, + "n_comment" VARCHAR(152), + PRIMARY KEY ("n_nationkey")); + """ + ) + + cs.execute( + """ + CREATE TABLE IF NOT EXISTS "region" ( + "r_regionkey" INT, + "r_name" CHAR(25), + "r_comment" VARCHAR(152), + PRIMARY KEY ("r_regionkey")); + """ + ) + + cs.execute( + """ + CREATE TABLE IF NOT EXISTS "supplier" ( + "s_suppkey" INT, + "s_name" CHAR(25), + "s_address" VARCHAR(40), + "s_nationkey" INT, + "s_phone" CHAR(15), + "s_acctbal" DECIMAL(15,2), + "s_comment" VARCHAR(101), + PRIMARY KEY ("s_suppkey")); + """ + ) + + cs.execute( + """ + CREATE TABLE IF NOT EXISTS "customer" ( + "c_custkey" INT, + "c_name" VARCHAR(25), + "c_address" VARCHAR(40), + "c_nationkey" INT, + "c_phone" CHAR(15), + "c_acctbal" DECIMAL(15,2), + "c_mktsegment" CHAR(10), + "c_comment" VARCHAR(117), + PRIMARY KEY ("c_custkey")); + """ + ) + + cs.execute( + """ + CREATE TABLE IF NOT EXISTS "part" ( + "p_partkey" INT, + "p_name" VARCHAR(55), + "p_mfgr" CHAR(25), + "p_brand" CHAR(10), + "p_type" VARCHAR(25), + "p_size" INT, + "p_container" CHAR(10), + "p_retailprice" DECIMAL(15,2) , + "p_comment" VARCHAR(23) , + PRIMARY KEY ("p_partkey")); + """ + ) + + cs.execute( + """ + CREATE TABLE IF NOT EXISTS "partsupp" ( + "ps_partkey" INT, + "ps_suppkey" INT, + "ps_availqty" INT, + "ps_supplycost" DECIMAL(15,2), + "ps_comment" VARCHAR(199), + PRIMARY KEY ("ps_partkey", "ps_suppkey")); + """ + ) + + cs.execute( + """ + CREATE TABLE IF NOT EXISTS "orders" ( + "o_orderkey" INT, + "o_custkey" INT, + "o_orderstatus" CHAR(1), + "o_totalprice" DECIMAL(15,2), + "o_orderdate" DATE, + "o_orderpriority" CHAR(15), + "o_clerk" CHAR(15), + "o_shippriority" INT, + "o_comment" VARCHAR(79), + PRIMARY KEY ("o_orderkey")); + """ + ) + + cs.execute( + """ + CREATE TABLE IF NOT EXISTS "lineitem"( + "l_orderkey" INT, + "l_partkey" INT, + "l_suppkey" INT, + "l_linenumber" INT, + "l_quantity" DECIMAL(15,2), + "l_extendedprice" DECIMAL(15,2), + "l_discount" DECIMAL(15,2), + "l_tax" DECIMAL(15,2), + "l_returnflag" CHAR(1), + "l_linestatus" CHAR(1), + "l_shipdate" DATE, + "l_commitdate" DATE, + "l_receiptdate" DATE, + "l_shipinstruct" CHAR(25), + "l_shipmode" CHAR(10), + "l_comment" VARCHAR(44) + ); + """ + ) + cs.execute( + """ + COPY nation + FROM 's3://geoffxy-research/shared/sf1/nation.csv' + IAM_ROLE 'arn:aws:iam::498725316081:role/service-role/AmazonRedshift-CommandsAccessRole-20230606T052021' + CSV + IGNOREHEADER 1; + """ + ) + cs.execute( + """ + COPY region + FROM 's3://geoffxy-research/shared/sf1/region.csv' + IAM_ROLE 'arn:aws:iam::498725316081:role/service-role/AmazonRedshift-CommandsAccessRole-20230606T052021' + CSV + IGNOREHEADER 1; + """ + ) + cs.execute( + """ + COPY supplier + FROM 's3://geoffxy-research/shared/sf1/supplier.csv' + IAM_ROLE 'arn:aws:iam::498725316081:role/service-role/AmazonRedshift-CommandsAccessRole-20230606T052021' + CSV + IGNOREHEADER 1; + """ + ) + cs.execute( + """ + COPY customer + FROM 's3://geoffxy-research/shared/sf1/customer.csv' + IAM_ROLE 'arn:aws:iam::498725316081:role/service-role/AmazonRedshift-CommandsAccessRole-20230606T052021' + CSV + IGNOREHEADER 1; + """ + ) + cs.execute( + """ + COPY part + FROM 's3://geoffxy-research/shared/sf1/part.csv' + IAM_ROLE 'arn:aws:iam::498725316081:role/service-role/AmazonRedshift-CommandsAccessRole-20230606T052021' + CSV + IGNOREHEADER 1; + """ + ) + cs.execute( + """ + COPY partsupp + FROM 's3://geoffxy-research/shared/sf1/partsupp.csv' + IAM_ROLE 'arn:aws:iam::498725316081:role/service-role/AmazonRedshift-CommandsAccessRole-20230606T052021' + CSV + IGNOREHEADER 1; + """ + ) + cs.execute( + """ + COPY orders + FROM 's3://geoffxy-research/shared/sf1/orders.csv' + IAM_ROLE 'arn:aws:iam::498725316081:role/service-role/AmazonRedshift-CommandsAccessRole-20230606T052021' + CSV + IGNOREHEADER 1; + """ + ) + cs.execute( + """ + COPY lineitem + FROM 's3://geoffxy-research/shared/sf1/lineitem.csv' + IAM_ROLE 'arn:aws:iam::498725316081:role/service-role/AmazonRedshift-CommandsAccessRole-20230606T052021' + CSV + IGNOREHEADER 1; + """ + ) + + +def time_query(cs, query_dir, i): + query_path = os.path.join(query_dir, f"{i}.sql") + if not os.path.exists(query_path): + print(f"File {query_path} does not exist.") + return + + with open(query_path, "r") as file: + query = file.read() + start_time = time.time() + cs.execute(query) + end_time = time.time() + elapsed_time = end_time - start_time + print(f"Query in file {i}.sql took {elapsed_time:.4f} seconds to execute.") + + +def run(cs): + # Where TPC-H queries are located + query_dir = "/spinning/axing/queries" + + for i in range(1, 23): + time_query(cs, query_dir, i) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description='Benchmark TPC-H on Redshift. takes in a argument "init"|"run"' + ) + parser.add_argument( + "task", + choices=["init", "run"], + help="init on first time running, run to run the benchmark", + ) + + args = parser.parse_args() + + conn = redshift_connector.connect( + host="redshift-axing.cv1pkocptzr2.us-east-1.redshift.amazonaws.com", + database="tpch", + port=5439, + user="awsuser", + password="axingUROP2024", + ) + + conn.rollback() + conn.autocommit = True + conn.run("VACUUM") + + cs = conn.cursor() + + if args.task == "init": + init(cs) + elif args.task == "run": + run(cs) + else: + print(f"Unknown task: {args.task}") From cdc1355e5df4d67daf6be53a078cf8d25d41b049 Mon Sep 17 00:00:00 2001 From: Albert Xing Date: Tue, 4 Jun 2024 05:48:04 -0400 Subject: [PATCH 12/13] Turn off caching for redshift results --- sandbox/qe/redshift.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sandbox/qe/redshift.py b/sandbox/qe/redshift.py index b0f106ab..a7426230 100644 --- a/sandbox/qe/redshift.py +++ b/sandbox/qe/redshift.py @@ -244,6 +244,8 @@ def run(cs): cs = conn.cursor() + cs.execute("SET enable_result_cache_for_session TO OFF") + if args.task == "init": init(cs) elif args.task == "run": From 3a29c5923a0ea6f6f7087a23df628f93d6a30954 Mon Sep 17 00:00:00 2001 From: Geoffrey Yu Date: Wed, 11 Sep 2024 16:38:55 -0400 Subject: [PATCH 13/13] Compilation fixes --- sandbox/qe/Cargo.toml | 13 ++++++++++--- sandbox/qe/RadixSplineLib/radixspline.cpp | 2 +- sandbox/qe/{build.rs => build_bindings.rs} | 4 +++- sandbox/qe/src/bin/test_radixspline.rs | 12 ------------ sandbox/qe/src/lib.rs | 3 ++- sandbox/qe/src/radixspline.rs | 2 +- 6 files changed, 17 insertions(+), 19 deletions(-) rename sandbox/qe/{build.rs => build_bindings.rs} (97%) delete mode 100644 sandbox/qe/src/bin/test_radixspline.rs diff --git a/sandbox/qe/Cargo.toml b/sandbox/qe/Cargo.toml index f068f020..f02fdada 100644 --- a/sandbox/qe/Cargo.toml +++ b/sandbox/qe/Cargo.toml @@ -2,7 +2,8 @@ name = "brad_qe" version = "0.1.0" edition = "2021" -build = "build.rs" +# NOTE: This currently does not work. (C++ standard library linking error) +# build = "build.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -24,5 +25,11 @@ cc = "1.0.96" [[bin]] name = "bench_q3" path = "src/bin/bench_q3.rs" -name = "test_radixspline" -path = "src/bin/test_radixspline.rs" + +[[bin]] +name = "brad_qe_repl" +path = "src/bin/repl.rs" + +# [[bin]] +# name = "test_radixspline" +# path = "src/bin/test_radixspline.rs" diff --git a/sandbox/qe/RadixSplineLib/radixspline.cpp b/sandbox/qe/RadixSplineLib/radixspline.cpp index 8603404b..38b55492 100644 --- a/sandbox/qe/RadixSplineLib/radixspline.cpp +++ b/sandbox/qe/RadixSplineLib/radixspline.cpp @@ -1,4 +1,4 @@ -#include "radixspline.hpp" +#include "radixspline.h" void* build(const uint64_t* ks, uint64_t size) { RSData* rs = new RSData; diff --git a/sandbox/qe/build.rs b/sandbox/qe/build_bindings.rs similarity index 97% rename from sandbox/qe/build.rs rename to sandbox/qe/build_bindings.rs index 482318cf..2b422f74 100644 --- a/sandbox/qe/build.rs +++ b/sandbox/qe/build_bindings.rs @@ -1,6 +1,8 @@ use std::env; use std::path::PathBuf; +// NOTE: This file should be named `build.rs` once it is working. + fn main() { // This is the directory where the `c` library is located. let libdir_path = PathBuf::from("RadixSplineLib") @@ -56,7 +58,7 @@ fn main() { // automatically know it must look for a `libradixspline.a` file. println!("cargo:rustc-link-lib=radixspline"); println!("cargo:rustc-link-lib=stdc++"); - + // The bindgen::Builder is the main entry point // to bindgen, and lets you build up options for // the resulting bindings. diff --git a/sandbox/qe/src/bin/test_radixspline.rs b/sandbox/qe/src/bin/test_radixspline.rs deleted file mode 100644 index 33a406ec..00000000 --- a/sandbox/qe/src/bin/test_radixspline.rs +++ /dev/null @@ -1,12 +0,0 @@ -use brad_qe::radixspline::RadixSpline; - -fn main() -> () { - let input: [u64; 15] = [1,2,3,4,6,7,8,9,11,12,13,15,17,20,21]; - let rspline = RadixSpline::build_simple(input); - println!("7 is in the array: {}", rspline.lookup(7)); - - println!("13 is in the array: {}", rspline.lookup(13)); - - println!("5 is in the array: {}", rspline.lookup(5)); - -} diff --git a/sandbox/qe/src/lib.rs b/sandbox/qe/src/lib.rs index 8af980bf..44aee2e2 100644 --- a/sandbox/qe/src/lib.rs +++ b/sandbox/qe/src/lib.rs @@ -23,7 +23,8 @@ pub mod ops; /// Utilities for rewriting DataFusion `ExecutionPlan`s. pub mod rewrite; -pub mod radixspline; +// RadixSpline bindings. Currently not working. +// pub mod radixspline; /// Represents an "open" IOHTAP database. Eventually, the DB should run as a /// daemon process. For now it is just an embedded DB (similar to SQLite). diff --git a/sandbox/qe/src/radixspline.rs b/sandbox/qe/src/radixspline.rs index dd3f4440..bee9e69a 100644 --- a/sandbox/qe/src/radixspline.rs +++ b/sandbox/qe/src/radixspline.rs @@ -25,7 +25,7 @@ impl RadixSpline { pub fn build(record_batch: &RecordBatch, column_index: usize) -> RadixSpline { let column = record_batch.column(column_index); let u64_array = column.as_any().downcast_ref::().unwrap(); - + let ptr = u64_array.values().as_ptr(); let size = column.len() as u64; unsafe {