Compare commits

...

2 commits

Author SHA1 Message Date
Joe Ardent
85473b4938 use uuids with original schema 2023-07-13 14:41:29 -07:00
Joe Ardent
a71420fc7b use import code from main, but schema from users_with_v4_uuids 2023-07-12 15:36:33 -07:00
8 changed files with 57 additions and 204 deletions

22
Cargo.lock generated
View file

@ -1868,6 +1868,7 @@ dependencies = [
"time",
"tokio-stream",
"url",
"uuid",
"webpki-roots",
]
@ -2228,15 +2229,6 @@ version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]]
name = "ulid"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13a3aaa69b04e5b66cc27309710a569ea23593612387d67daaf102e73aa974fd"
dependencies = [
"rand",
]
[[package]]
name = "unicase"
version = "2.6.0"
@ -2308,6 +2300,16 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "uuid"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d023da39d1fde5a8a3fe1f3e01ca9632ada0a63e9797de55a879d6e2236277be"
dependencies = [
"getrandom",
"serde",
]
[[package]]
name = "valuable"
version = "0.1.0"
@ -2454,8 +2456,8 @@ dependencies = [
"tower-http 0.4.1",
"tracing",
"tracing-subscriber",
"ulid",
"unicode-segmentation",
"uuid",
]
[[package]]

View file

@ -22,7 +22,8 @@ justerror = "1"
password-hash = { version = "0.5", features = ["std", "getrandom"] }
rand = "0.8"
serde = { version = "1", features = ["derive"] }
sqlx = { version = "0.6", default-features = false, features = ["runtime-tokio-rustls", "any", "sqlite", "chrono", "time"] }
sqlx = { version = "0.6", default-features = false, features = ["runtime-tokio-rustls", "any",
"sqlite", "chrono", "time", "uuid"] }
thiserror = "1"
tokio = { version = "1", features = ["full", "tracing"], default-features = false }
tokio-retry = "0.3.0"
@ -31,9 +32,9 @@ tower = { version = "0.4", features = ["util", "timeout"], default-features = fa
tower-http = { version = "0.4", features = ["add-extension", "trace"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
ulid = { version = "1", features = ["rand"] }
unicode-segmentation = "1"
rand_distr = "0.4.3"
uuid = { version = "1.4.0", features = ["serde", "v4"] }
[dev-dependencies]
axum-test = "9.0.0"

View file

@ -1,14 +1,10 @@
-- indices
drop index if exists user_username_dex;
drop index if exists user_email_dex;
drop index if exists watch_title_dex;
drop index if exists witch_added_by_dex;
drop index if exists quests_user_dex;
drop index if exists quests_watch_dex;
drop index if exists note_user_dex;
drop index if exists note_watch_dex;
drop index if exists user_dex;
drop index if exists watch_dex;
drop index if exists w2wdex;
drop index if exists note_dex;
-- tables
drop table if exists watch_quests;
drop table if exists watch_quest;
drop table if exists watch_notes;
drop table if exists follows;
drop table if exists users;

View file

@ -29,24 +29,25 @@ create table if not exists watches (
-- table of what people want to watch
create table if not exists watch_quests (
id blob not null primary key,
user blob not null,
watch blob not null,
party blob, -- list of user IDs, but we can also scan for friends that want to watch the same thing
priority int, -- 1-5 how much do you want to watch it
public boolean not null default true,
watched boolean not null default false,
when_added int not null default (unixepoch()),
public boolean not null,
watched boolean not null,
when_added int,
when_watched int,
last_updated int not null default (unixepoch()),
foreign key (user) references users (id) on delete cascade on update no action,
foreign key (watch) references watches (id) on delete cascade on update no action,
primary key (user, watch)
) without rowid;
foreign key (watch) references watches (id) on delete cascade on update no action
);
-- friend lists; this should really be a graph db, maybe the whole thing should be
-- TODO: look into replacing sqlite with https://www.cozodb.org/
create table if not exists follows (
user blob not null primary key,
follows blob, -- possibly empty friends list in some app-specific format
coven blob, -- possibly empty friends list in some app-specific format
last_updated int not null default (unixepoch()),
foreign key (user) references users (id) on delete cascade on update no action
);
@ -64,10 +65,10 @@ create table if not exists watch_notes (
-- indices, not needed for follows
create index if not exists user_username_dex on users (username);
create index if not exists user_email_dex on users (lower(email));
create index if not exists watch_title_dex on watches (lower(title));
create index if not exists user_email_dex on users (email);
create index if not exists watch_title_dex on watches (title);
create index if not exists watch_added_by_dex on watches (added_by);
create index if not exists quests_user_dex on watch_quests (user);
create index if not exists quests_watch_dex on watch_quests (watch);
create index if not exists questswatch_dex on watch_quests (watch);
create index if not exists note_user_dex on watch_notes (user);
create index if not exists note_watch_dex on watch_notes (watch);

View file

@ -1,5 +1,5 @@
drop trigger if exists update_last_updated_users;
drop trigger if exists update_last_updated_watches;
drop trigger if exists update_last_updated_watch_quests;
drop trigger if exists update_last_updated_watch_quest;
drop trigger if exists update_last_updated_follows;
drop trigger if exists update_last_updated_watch_notes;

View file

@ -16,7 +16,7 @@ create trigger if not exists update_last_updated_watch_quests
after update on watch_quests
when OLD.last_updated = NEW.last_updated or OLD.last_updated is null
BEGIN
update watch_quests set last_updated = (select unixepoch()) where id=NEW.id;
update watch_quest set last_updated = (select unixepoch()) where id=NEW.id;
END;
create trigger if not exists update_last_updated_follows

View file

@ -1,48 +1,34 @@
use std::{
borrow::Cow,
fmt::{Debug, Display},
};
use std::fmt::{Debug, Display};
use chrono::Utc;
use serde::{de::Visitor, Deserialize, Serialize};
use sqlx::{
encode::IsNull,
sqlite::{SqliteArgumentValue, SqliteValueRef},
Decode, Encode, Sqlite,
};
use ulid::Ulid;
use serde::{Deserialize, Serialize};
use uuid::{Error, Uuid};
#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DbId(pub Ulid);
#[derive(
Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, sqlx::Type, Serialize, Deserialize,
)]
#[sqlx(transparent)]
pub struct DbId(pub Uuid);
impl DbId {
pub fn bytes(&self) -> [u8; 16] {
self.to_be_bytes()
}
pub fn to_be_bytes(self) -> [u8; 16] {
self.0 .0.to_be_bytes()
}
pub fn is_nil(&self) -> bool {
self.0.is_nil()
}
pub fn new() -> Self {
Self(Ulid::new())
Self(Uuid::new_v4())
}
pub fn from_string(s: &str) -> Result<Self, ulid::DecodeError> {
let id = Ulid::from_string(s)?;
Ok(id.into())
pub fn from_string(s: &str) -> Result<Self, Error> {
Ok(Self(Uuid::try_parse(s)?))
}
pub fn as_string(&self) -> String {
self.0.to_string()
self.0.simple().to_string()
}
pub fn created_at(&self) -> chrono::DateTime<Utc> {
self.0.datetime().into()
chrono::DateTime::default()
}
}
@ -62,153 +48,14 @@ impl Debug for DbId {
}
}
impl From<Ulid> for DbId {
fn from(value: Ulid) -> Self {
impl From<Uuid> for DbId {
fn from(value: Uuid) -> Self {
DbId(value)
}
}
impl From<u128> for DbId {
fn from(value: u128) -> Self {
DbId(value.into())
}
}
//-************************************************************************
// sqlx traits for going in and out of the db
//-************************************************************************
impl sqlx::Type<sqlx::Sqlite> for DbId {
fn type_info() -> <sqlx::Sqlite as sqlx::Database>::TypeInfo {
<&[u8] as sqlx::Type<sqlx::Sqlite>>::type_info()
}
}
impl<'q> Encode<'q, Sqlite> for DbId {
fn encode_by_ref(&self, args: &mut Vec<SqliteArgumentValue<'q>>) -> IsNull {
args.push(SqliteArgumentValue::Blob(Cow::Owned(self.bytes().to_vec())));
IsNull::No
}
}
impl Decode<'_, Sqlite> for DbId {
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(u128::from_be_bytes(bytes).into())
}
}
//-************************************************************************
// serde traits
//-************************************************************************
impl Serialize for DbId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_bytes(&self.bytes())
}
}
struct DbIdVisitor;
impl<'de> Visitor<'de> for DbIdVisitor {
type Value = DbId;
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(u128::from_be_bytes(v).into()),
Err(_) => Err(serde::de::Error::invalid_length(v.len(), &self)),
}
}
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
DbId::from_string(&v)
.map_err(|_| serde::de::Error::invalid_value(serde::de::Unexpected::Str(&v), &self))
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
DbId::from_string(v)
.map_err(|_| serde::de::Error::invalid_value(serde::de::Unexpected::Str(v), &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(u128::from_be_bytes(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 raw_bytes_from_db = [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;
}
raw_bytes_from_db[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(u128::from_be_bytes(raw_bytes_from_db).into())
}
}
}
impl<'de> Deserialize<'de> for DbId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_bytes(DbIdVisitor)
}
}
//-************************************************************************
// serialization tests
//-************************************************************************
#[cfg(test)]
mod test {
use serde_test::{assert_tokens, Token};
use super::*;
#[test]
fn test_ser_de() {
let bytes: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
let id: DbId = u128::from_be_bytes(bytes).into();
assert_tokens(
&id,
&[Token::Bytes(&[
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
])],
);
DbId(Uuid::from_u128(value))
}
}

View file

@ -52,12 +52,18 @@ impl From<&ImportMovieOmega> for Watch {
// utility functions for building CLI tools, currently just for benchmarking
//-************************************************************************
pub async fn add_watch_quests(pool: &SqlitePool, quests: &[WatchQuest]) -> Result<(), ()> {
let mut builder = sqlx::QueryBuilder::new("insert into watch_quests (user, watch) ");
let mut builder =
sqlx::QueryBuilder::new("insert into watch_quests (id, user, watch, public, watched) ");
builder.push_values(quests, |mut b, quest| {
let id = DbId::new();
let user = quest.user;
let watch = quest.watch;
//eprintln!("{user}, {watch}");
b.push_bind(user).push_bind(watch);
b.push_bind(id)
.push_bind(user)
.push_bind(watch)
.push_bind(true)
.push_bind(false);
});
let q = builder.build();