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:
Joe Ardent 2023-07-26 18:21:42 -07:00
parent 98a8a26fe9
commit d6f9a00277
4 changed files with 174 additions and 2 deletions

View file

@ -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"

View file

@ -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
View 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
View 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())
}
}