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]
|
||||
name = "julid-rs"
|
||||
version = "0.0.1"
|
||||
version = "0.1.6"
|
||||
authors = ["Joe Ardent <code@ardent.nebcorp.com>"]
|
||||
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."
|
||||
readme = "README.md"
|
||||
license-file = "LICENSE.md"
|
||||
repository = "https://gitlab.com/nebkor/julid"
|
||||
|
||||
[features]
|
||||
default = ["serde", "sqlx"]
|
||||
clib = []
|
||||
|
||||
[lib]
|
||||
name = "julid"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
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"
|
||||
|
|
14
src/lib.rs
14
src/lib.rs
|
@ -1,3 +1,4 @@
|
|||
#[cfg(feature = "clib")]
|
||||
use sqlite_loadable::{
|
||||
api, define_scalar_function,
|
||||
prelude::{
|
||||
|
@ -9,12 +10,19 @@ use sqlite_loadable::{
|
|||
|
||||
mod base32;
|
||||
pub mod julid;
|
||||
#[cfg(feature = "serde")]
|
||||
pub mod serde;
|
||||
|
||||
#[cfg(feature = "sqlx")]
|
||||
pub mod sqlx;
|
||||
|
||||
pub use base32::JULID_LEN;
|
||||
pub use julid::Julid;
|
||||
|
||||
//-************************************************************************
|
||||
// Entrypoint into the loadable extension
|
||||
//-************************************************************************
|
||||
#[cfg(feature = "clib")]
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn sqlite3_julid_init(
|
||||
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<()> {
|
||||
let flags = FunctionFlags::UTF8 | FunctionFlags::DETERMINISTIC;
|
||||
define_scalar_function(db, "julid_new", 0, julid_new, FunctionFlags::INNOCUOUS)?;
|
||||
|
@ -42,11 +51,13 @@ fn init_rs(db: *mut sqlite3) -> Result<()> {
|
|||
//-************************************************************************
|
||||
// impls
|
||||
//-************************************************************************
|
||||
#[cfg(feature = "clib")]
|
||||
fn julid_new(context: *mut sqlite3_context, _vals: &[*mut sqlite3_value]) -> Result<()> {
|
||||
api::result_blob(context, Julid::new().to_bytes().as_slice());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "clib")]
|
||||
fn julid_timestamp(context: *mut sqlite3_context, id: &[*mut sqlite3_value]) -> Result<()> {
|
||||
if let Some(value) = id.get(0) {
|
||||
let id = api::value_blob(value);
|
||||
|
@ -64,6 +75,7 @@ fn julid_timestamp(context: *mut sqlite3_context, id: &[*mut sqlite3_value]) ->
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "clib")]
|
||||
fn julid_counter(context: *mut sqlite3_context, id: &[*mut sqlite3_value]) -> Result<()> {
|
||||
if let Some(value) = id.get(0) {
|
||||
let id = api::value_blob(value);
|
||||
|
@ -81,6 +93,7 @@ fn julid_counter(context: *mut sqlite3_context, id: &[*mut sqlite3_value]) -> Re
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "clib")]
|
||||
fn julid_sortable(context: *mut sqlite3_context, id: &[*mut sqlite3_value]) -> Result<()> {
|
||||
if let Some(value) = id.get(0) {
|
||||
let id = api::value_blob(value);
|
||||
|
@ -98,6 +111,7 @@ fn julid_sortable(context: *mut sqlite3_context, id: &[*mut sqlite3_value]) -> R
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "clib")]
|
||||
fn julid_string(context: *mut sqlite3_context, id: &[*mut sqlite3_value]) -> Result<()> {
|
||||
if let Some(value) = id.get(0) {
|
||||
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