Make sqlite extension a feature.
This means that you can use it in a Rust app that links against libsqlite, but you need to compile it as an extension using a feature, `clib`.
This commit is contained in:
parent
98a8a26fe9
commit
d6f9a00277
4 changed files with 174 additions and 2 deletions
10
Cargo.toml
10
Cargo.toml
|
@ -1,19 +1,25 @@
|
||||||
[package]
|
[package]
|
||||||
name = "julid-rs"
|
name = "julid-rs"
|
||||||
version = "0.0.1"
|
version = "0.1.6"
|
||||||
authors = ["Joe Ardent <code@ardent.nebcorp.com>"]
|
authors = ["Joe Ardent <code@ardent.nebcorp.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
keywords = ["ulid", "library", "sqlite", "extension"]
|
keywords = ["ulid", "library", "sqlite", "extension", "julid"]
|
||||||
|
|
||||||
description = "A library and loadable extension for SQLite that uses it, that provides Joe's ULIDs."
|
description = "A library and loadable extension for SQLite that uses it, that provides Joe's ULIDs."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license-file = "LICENSE.md"
|
license-file = "LICENSE.md"
|
||||||
repository = "https://gitlab.com/nebkor/julid"
|
repository = "https://gitlab.com/nebkor/julid"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["serde", "sqlx"]
|
||||||
|
clib = []
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "julid"
|
name = "julid"
|
||||||
crate-type = ["cdylib", "rlib"]
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
|
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||||
|
sqlx = { version = "0.7", features = ["sqlite"], default-features = false, optional = true }
|
||||||
sqlite-loadable = "0.0.5"
|
sqlite-loadable = "0.0.5"
|
||||||
|
|
14
src/lib.rs
14
src/lib.rs
|
@ -1,3 +1,4 @@
|
||||||
|
#[cfg(feature = "clib")]
|
||||||
use sqlite_loadable::{
|
use sqlite_loadable::{
|
||||||
api, define_scalar_function,
|
api, define_scalar_function,
|
||||||
prelude::{
|
prelude::{
|
||||||
|
@ -9,12 +10,19 @@ use sqlite_loadable::{
|
||||||
|
|
||||||
mod base32;
|
mod base32;
|
||||||
pub mod julid;
|
pub mod julid;
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
pub mod serde;
|
||||||
|
|
||||||
|
#[cfg(feature = "sqlx")]
|
||||||
|
pub mod sqlx;
|
||||||
|
|
||||||
|
pub use base32::JULID_LEN;
|
||||||
pub use julid::Julid;
|
pub use julid::Julid;
|
||||||
|
|
||||||
//-************************************************************************
|
//-************************************************************************
|
||||||
// Entrypoint into the loadable extension
|
// Entrypoint into the loadable extension
|
||||||
//-************************************************************************
|
//-************************************************************************
|
||||||
|
#[cfg(feature = "clib")]
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn sqlite3_julid_init(
|
pub unsafe extern "C" fn sqlite3_julid_init(
|
||||||
db: *mut sqlite3,
|
db: *mut sqlite3,
|
||||||
|
@ -28,6 +36,7 @@ pub unsafe extern "C" fn sqlite3_julid_init(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "clib")]
|
||||||
fn init_rs(db: *mut sqlite3) -> Result<()> {
|
fn init_rs(db: *mut sqlite3) -> Result<()> {
|
||||||
let flags = FunctionFlags::UTF8 | FunctionFlags::DETERMINISTIC;
|
let flags = FunctionFlags::UTF8 | FunctionFlags::DETERMINISTIC;
|
||||||
define_scalar_function(db, "julid_new", 0, julid_new, FunctionFlags::INNOCUOUS)?;
|
define_scalar_function(db, "julid_new", 0, julid_new, FunctionFlags::INNOCUOUS)?;
|
||||||
|
@ -42,11 +51,13 @@ fn init_rs(db: *mut sqlite3) -> Result<()> {
|
||||||
//-************************************************************************
|
//-************************************************************************
|
||||||
// impls
|
// impls
|
||||||
//-************************************************************************
|
//-************************************************************************
|
||||||
|
#[cfg(feature = "clib")]
|
||||||
fn julid_new(context: *mut sqlite3_context, _vals: &[*mut sqlite3_value]) -> Result<()> {
|
fn julid_new(context: *mut sqlite3_context, _vals: &[*mut sqlite3_value]) -> Result<()> {
|
||||||
api::result_blob(context, Julid::new().to_bytes().as_slice());
|
api::result_blob(context, Julid::new().to_bytes().as_slice());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "clib")]
|
||||||
fn julid_timestamp(context: *mut sqlite3_context, id: &[*mut sqlite3_value]) -> Result<()> {
|
fn julid_timestamp(context: *mut sqlite3_context, id: &[*mut sqlite3_value]) -> Result<()> {
|
||||||
if let Some(value) = id.get(0) {
|
if let Some(value) = id.get(0) {
|
||||||
let id = api::value_blob(value);
|
let id = api::value_blob(value);
|
||||||
|
@ -64,6 +75,7 @@ fn julid_timestamp(context: *mut sqlite3_context, id: &[*mut sqlite3_value]) ->
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "clib")]
|
||||||
fn julid_counter(context: *mut sqlite3_context, id: &[*mut sqlite3_value]) -> Result<()> {
|
fn julid_counter(context: *mut sqlite3_context, id: &[*mut sqlite3_value]) -> Result<()> {
|
||||||
if let Some(value) = id.get(0) {
|
if let Some(value) = id.get(0) {
|
||||||
let id = api::value_blob(value);
|
let id = api::value_blob(value);
|
||||||
|
@ -81,6 +93,7 @@ fn julid_counter(context: *mut sqlite3_context, id: &[*mut sqlite3_value]) -> Re
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "clib")]
|
||||||
fn julid_sortable(context: *mut sqlite3_context, id: &[*mut sqlite3_value]) -> Result<()> {
|
fn julid_sortable(context: *mut sqlite3_context, id: &[*mut sqlite3_value]) -> Result<()> {
|
||||||
if let Some(value) = id.get(0) {
|
if let Some(value) = id.get(0) {
|
||||||
let id = api::value_blob(value);
|
let id = api::value_blob(value);
|
||||||
|
@ -98,6 +111,7 @@ fn julid_sortable(context: *mut sqlite3_context, id: &[*mut sqlite3_value]) -> R
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "clib")]
|
||||||
fn julid_string(context: *mut sqlite3_context, id: &[*mut sqlite3_value]) -> Result<()> {
|
fn julid_string(context: *mut sqlite3_context, id: &[*mut sqlite3_value]) -> Result<()> {
|
||||||
if let Some(value) = id.get(0) {
|
if let Some(value) = id.get(0) {
|
||||||
let id = api::value_blob(value);
|
let id = api::value_blob(value);
|
||||||
|
|
120
src/serde.rs
Normal file
120
src/serde.rs
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
//! Serialization and deserialization.
|
||||||
|
//!
|
||||||
|
//! By default, serialization and deserialization go through Julid's big-endian
|
||||||
|
//! bytes representation.
|
||||||
|
|
||||||
|
use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
|
||||||
|
use crate::Julid;
|
||||||
|
|
||||||
|
impl Serialize for Julid {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_bytes(&self.to_bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct JulidVisitor;
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for JulidVisitor {
|
||||||
|
type Value = Julid;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
formatter.write_str("16 bytes")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: serde::de::Error,
|
||||||
|
{
|
||||||
|
match std::convert::TryInto::<[u8; 16]>::try_into(v) {
|
||||||
|
Ok(v) => Ok(v.into()),
|
||||||
|
Err(_) => Err(serde::de::Error::invalid_length(v.len(), &self)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: serde::de::Error,
|
||||||
|
{
|
||||||
|
let len = v.len();
|
||||||
|
match std::convert::TryInto::<[u8; 16]>::try_into(v) {
|
||||||
|
Ok(v) => Ok(v.into()),
|
||||||
|
Err(_) => Err(serde::de::Error::invalid_length(len, &self)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||||
|
where
|
||||||
|
A: serde::de::SeqAccess<'de>,
|
||||||
|
{
|
||||||
|
let mut bytes = [0u8; 16];
|
||||||
|
let size = seq.size_hint().unwrap_or(0);
|
||||||
|
let mut count = 0;
|
||||||
|
while let Some(val) = seq.next_element()? {
|
||||||
|
if count > 15 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
bytes[count] = val;
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
if count != 16 || size > 16 {
|
||||||
|
let sz = if count < 16 { count } else { size };
|
||||||
|
Err(serde::de::Error::invalid_length(sz, &self))
|
||||||
|
} else {
|
||||||
|
Ok(bytes.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Julid {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
deserializer.deserialize_bytes(JulidVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialization and deserialization of Julids through their string
|
||||||
|
/// representation.
|
||||||
|
///
|
||||||
|
/// To use it, annotate a field with
|
||||||
|
/// `#[serde(with = "julid_as_str")]`,
|
||||||
|
/// `#[serde(serialize_with = "julid_as_str")]`, or
|
||||||
|
/// `#[serde(deserialize_with = "julid_as_str")]`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// # use julid::Julid;
|
||||||
|
/// # use julid::serde::ulid_as_str;
|
||||||
|
/// # use serde::{Serialize, Deserialize};
|
||||||
|
/// #[derive(Serialize, Deserialize)]
|
||||||
|
/// struct StrExample {
|
||||||
|
/// #[serde(with = "julid_as_str")]
|
||||||
|
/// identifier: Julid
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub mod julid_as_str {
|
||||||
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
|
||||||
|
use crate::Julid;
|
||||||
|
|
||||||
|
pub fn serialize<S>(value: &Julid, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let text = value.to_string();
|
||||||
|
text.serialize(serializer)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<Julid, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let deserialized_str = String::deserialize(deserializer)?;
|
||||||
|
Julid::from_string(&deserialized_str).map_err(serde::de::Error::custom)
|
||||||
|
}
|
||||||
|
}
|
32
src/sqlx.rs
Normal file
32
src/sqlx.rs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use sqlx::{
|
||||||
|
encode::IsNull,
|
||||||
|
sqlite::{SqliteArgumentValue, SqliteValueRef},
|
||||||
|
Decode, Encode, Sqlite,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::Julid;
|
||||||
|
|
||||||
|
impl sqlx::Type<sqlx::Sqlite> for Julid {
|
||||||
|
fn type_info() -> <sqlx::Sqlite as sqlx::Database>::TypeInfo {
|
||||||
|
<&[u8] as sqlx::Type<sqlx::Sqlite>>::type_info()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'q> Encode<'q, Sqlite> for Julid {
|
||||||
|
fn encode_by_ref(&self, args: &mut Vec<SqliteArgumentValue<'q>>) -> IsNull {
|
||||||
|
args.push(SqliteArgumentValue::Blob(Cow::Owned(
|
||||||
|
self.to_bytes().to_vec(),
|
||||||
|
)));
|
||||||
|
IsNull::No
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decode<'_, Sqlite> for Julid {
|
||||||
|
fn decode(value: SqliteValueRef<'_>) -> Result<Self, sqlx::error::BoxDynError> {
|
||||||
|
let bytes = <&[u8] as Decode<Sqlite>>::decode(value)?;
|
||||||
|
let bytes: [u8; 16] = bytes.try_into().unwrap_or_default();
|
||||||
|
Ok(bytes.into())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue