mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-04 12:13:27 +00:00
Remove owned sync (#967)
* remove owned sync + cleanup * no more atomic records * byebye owned * remove owned ops from schema
This commit is contained in:
parent
2d703f2648
commit
d683d22c82
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the `owned_operation` table. If the table is not empty, all the data it contains will be lost.
|
||||
- You are about to drop the column `date_created` on the `indexer_rule_in_location` table. All the data in the column will be lost.
|
||||
- Made the column `pub_id` on table `indexer_rule` required. This step will fail if there are existing NULL values in that column.
|
||||
|
||||
*/
|
||||
-- DropTable
|
||||
PRAGMA foreign_keys=off;
|
||||
DROP TABLE "owned_operation";
|
||||
PRAGMA foreign_keys=on;
|
||||
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_indexer_rule" (
|
||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"pub_id" BLOB NOT NULL,
|
||||
"name" TEXT,
|
||||
"default" BOOLEAN,
|
||||
"rules_per_kind" BLOB,
|
||||
"date_created" DATETIME,
|
||||
"date_modified" DATETIME
|
||||
);
|
||||
INSERT INTO "new_indexer_rule" ("date_created", "date_modified", "default", "id", "name", "pub_id", "rules_per_kind") SELECT "date_created", "date_modified", "default", "id", "name", "pub_id", "rules_per_kind" FROM "indexer_rule";
|
||||
DROP TABLE "indexer_rule";
|
||||
ALTER TABLE "new_indexer_rule" RENAME TO "indexer_rule";
|
||||
CREATE UNIQUE INDEX "indexer_rule_pub_id_key" ON "indexer_rule"("pub_id");
|
||||
CREATE TABLE "new_job" (
|
||||
"id" BLOB NOT NULL PRIMARY KEY,
|
||||
"name" TEXT,
|
||||
"node_id" INTEGER,
|
||||
"action" TEXT,
|
||||
"status" INTEGER,
|
||||
"errors_text" TEXT,
|
||||
"data" BLOB,
|
||||
"metadata" BLOB,
|
||||
"parent_id" BLOB,
|
||||
"task_count" INTEGER,
|
||||
"completed_task_count" INTEGER,
|
||||
"date_estimated_completion" DATETIME,
|
||||
"date_created" DATETIME,
|
||||
"date_started" DATETIME,
|
||||
"date_completed" DATETIME,
|
||||
CONSTRAINT "job_node_id_fkey" FOREIGN KEY ("node_id") REFERENCES "node" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT "job_parent_id_fkey" FOREIGN KEY ("parent_id") REFERENCES "job" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_job" ("action", "completed_task_count", "data", "date_completed", "date_created", "date_estimated_completion", "date_started", "errors_text", "id", "metadata", "name", "node_id", "parent_id", "status", "task_count") SELECT "action", "completed_task_count", "data", "date_completed", "date_created", "date_estimated_completion", "date_started", "errors_text", "id", "metadata", "name", "node_id", "parent_id", "status", "task_count" FROM "job";
|
||||
DROP TABLE "job";
|
||||
ALTER TABLE "new_job" RENAME TO "job";
|
||||
CREATE TABLE "new_indexer_rule_in_location" (
|
||||
"location_id" INTEGER NOT NULL,
|
||||
"indexer_rule_id" INTEGER NOT NULL,
|
||||
|
||||
PRIMARY KEY ("location_id", "indexer_rule_id"),
|
||||
CONSTRAINT "indexer_rule_in_location_location_id_fkey" FOREIGN KEY ("location_id") REFERENCES "location" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION,
|
||||
CONSTRAINT "indexer_rule_in_location_indexer_rule_id_fkey" FOREIGN KEY ("indexer_rule_id") REFERENCES "indexer_rule" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
|
||||
);
|
||||
INSERT INTO "new_indexer_rule_in_location" ("indexer_rule_id", "location_id") SELECT "indexer_rule_id", "location_id" FROM "indexer_rule_in_location";
|
||||
DROP TABLE "indexer_rule_in_location";
|
||||
ALTER TABLE "new_indexer_rule_in_location" RENAME TO "indexer_rule_in_location";
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
|
@ -16,18 +16,6 @@ generator sync {
|
|||
|
||||
//// Sync ////
|
||||
|
||||
model OwnedOperation {
|
||||
id Bytes @id
|
||||
timestamp BigInt
|
||||
data Bytes
|
||||
model String
|
||||
|
||||
node_id Int
|
||||
node Node @relation(fields: [node_id], references: [id])
|
||||
|
||||
@@map("owned_operation")
|
||||
}
|
||||
|
||||
model SharedOperation {
|
||||
id Bytes @id
|
||||
timestamp BigInt
|
||||
|
@ -73,7 +61,6 @@ model Node {
|
|||
jobs Job[]
|
||||
Location Location[]
|
||||
|
||||
OwnedOperation OwnedOperation[]
|
||||
SharedOperation SharedOperation[]
|
||||
|
||||
@@map("node")
|
||||
|
@ -101,7 +88,6 @@ model Location {
|
|||
id Int @id @default(autoincrement())
|
||||
pub_id Bytes @unique
|
||||
|
||||
node_id Int?
|
||||
name String?
|
||||
path String?
|
||||
total_capacity Int?
|
||||
|
@ -112,7 +98,9 @@ model Location {
|
|||
hidden Boolean?
|
||||
date_created DateTime?
|
||||
|
||||
node Node? @relation(fields: [node_id], references: [id])
|
||||
node_id Int?
|
||||
node Node? @relation(fields: [node_id], references: [id])
|
||||
|
||||
file_paths FilePath[]
|
||||
indexer_rules IndexerRulesInLocation[]
|
||||
|
||||
|
@ -402,7 +390,6 @@ model Job {
|
|||
|
||||
//// Album ////
|
||||
|
||||
/// @shared(id: pub_id)
|
||||
// model Album {
|
||||
// id Int @id @default(autoincrement())
|
||||
// pub_id Bytes @unique
|
||||
|
|
|
@ -248,25 +248,22 @@ async fn identifier_job_step(
|
|||
|
||||
let kind = meta.kind as i32;
|
||||
|
||||
let object_creation_args = (
|
||||
[sync.shared_create(sync_id())]
|
||||
.into_iter()
|
||||
.chain(
|
||||
[
|
||||
(object::date_created::NAME, json!(fp.date_created)),
|
||||
(object::kind::NAME, json!(kind)),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|(f, v)| sync.shared_update(sync_id(), f, v)),
|
||||
)
|
||||
.collect::<Vec<_>>(),
|
||||
object::create_unchecked(
|
||||
uuid_to_bytes(object_pub_id),
|
||||
vec![
|
||||
object::date_created::set(fp.date_created),
|
||||
object::kind::set(Some(kind)),
|
||||
],
|
||||
let (sync_params, db_params): (Vec<_>, Vec<_>) = [
|
||||
(
|
||||
(object::date_created::NAME, json!(fp.date_created)),
|
||||
object::date_created::set(fp.date_created),
|
||||
),
|
||||
(
|
||||
(object::kind::NAME, json!(kind)),
|
||||
object::kind::set(Some(kind)),
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.unzip();
|
||||
|
||||
let object_creation_args = (
|
||||
sync.unique_shared_create(sync_id(), sync_params),
|
||||
object::create_unchecked(uuid_to_bytes(object_pub_id), db_params),
|
||||
);
|
||||
|
||||
(object_creation_args, {
|
||||
|
@ -287,7 +284,7 @@ async fn identifier_job_step(
|
|||
.write_ops(db, {
|
||||
let (sync, db_params): (Vec<_>, Vec<_>) = object_create_args.into_iter().unzip();
|
||||
|
||||
(sync.concat(), db.object().create_many(db_params))
|
||||
(sync, db.object().create_many(db_params))
|
||||
})
|
||||
.await
|
||||
.unwrap_or_else(|e| {
|
||||
|
|
|
@ -53,7 +53,7 @@ pub enum P2PEvent {
|
|||
}
|
||||
|
||||
pub struct P2PManager {
|
||||
pub events: broadcast::Sender<P2PEvent>,
|
||||
pub events: (broadcast::Sender<P2PEvent>, broadcast::Receiver<P2PEvent>),
|
||||
pub manager: Arc<Manager<PeerMetadata>>,
|
||||
spacedrop_pairing_reqs: Arc<Mutex<HashMap<Uuid, oneshot::Sender<Option<String>>>>>,
|
||||
pub metadata_manager: Arc<MetadataManager<PeerMetadata>>,
|
||||
|
@ -80,11 +80,13 @@ impl P2PManager {
|
|||
manager.listen_addrs().await
|
||||
);
|
||||
|
||||
let (tx, _) = broadcast::channel(100);
|
||||
// need to keep 'rx' around so that the channel isn't dropped
|
||||
let (tx, rx) = broadcast::channel(100);
|
||||
let (tx2, rx2) = broadcast::channel(100);
|
||||
|
||||
let spacedrop_pairing_reqs = Arc::new(Mutex::new(HashMap::new()));
|
||||
let spacedrop_progress = Arc::new(Mutex::new(HashMap::new()));
|
||||
|
||||
tokio::spawn({
|
||||
let events = tx.clone();
|
||||
// let sync_events = tx2.clone();
|
||||
|
@ -226,7 +228,7 @@ impl P2PManager {
|
|||
// https://docs.rs/system_shutdown/latest/system_shutdown/
|
||||
|
||||
let this = Arc::new(Self {
|
||||
events: tx,
|
||||
events: (tx, rx),
|
||||
manager,
|
||||
spacedrop_pairing_reqs,
|
||||
metadata_manager,
|
||||
|
@ -275,7 +277,7 @@ impl P2PManager {
|
|||
}
|
||||
|
||||
pub fn subscribe(&self) -> broadcast::Receiver<P2PEvent> {
|
||||
self.events.subscribe()
|
||||
self.events.0.subscribe()
|
||||
}
|
||||
|
||||
#[allow(unused)] // TODO: Remove `allow(unused)` once integrated
|
||||
|
|
|
@ -50,21 +50,6 @@ impl SyncManager {
|
|||
) -> prisma_client_rust::Result<<I as prisma_client_rust::BatchItemParent>::ReturnValue> {
|
||||
#[cfg(feature = "sync-messages")]
|
||||
let res = {
|
||||
let owned = _ops
|
||||
.iter()
|
||||
.filter_map(|op| match &op.typ {
|
||||
CRDTOperationType::Owned(owned_op) => Some(tx.owned_operation().create(
|
||||
op.id.as_bytes().to_vec(),
|
||||
op.timestamp.0 as i64,
|
||||
to_vec(&owned_op.items).unwrap(),
|
||||
owned_op.model.clone(),
|
||||
node::pub_id::equals(op.node.as_bytes().to_vec()),
|
||||
vec![],
|
||||
)),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let shared = _ops
|
||||
.iter()
|
||||
.filter_map(|op| match &op.typ {
|
||||
|
@ -90,7 +75,7 @@ impl SyncManager {
|
|||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (res, _) = tx._batch((queries, (owned, shared))).await?;
|
||||
let (res, _) = tx._batch((queries, shared)).await?;
|
||||
|
||||
for op in _ops {
|
||||
self.tx.send(SyncMessage::Created(op)).ok();
|
||||
|
@ -111,21 +96,6 @@ impl SyncManager {
|
|||
query: Q,
|
||||
) -> prisma_client_rust::Result<<Q as prisma_client_rust::BatchItemParent>::ReturnValue> {
|
||||
let ret = match &op.typ {
|
||||
CRDTOperationType::Owned(owned_op) => {
|
||||
tx._batch((
|
||||
tx.owned_operation().create(
|
||||
op.id.as_bytes().to_vec(),
|
||||
op.timestamp.0 as i64,
|
||||
to_vec(&owned_op.items).unwrap(),
|
||||
owned_op.model.clone(),
|
||||
node::pub_id::equals(op.node.as_bytes().to_vec()),
|
||||
vec![],
|
||||
),
|
||||
query,
|
||||
))
|
||||
.await?
|
||||
.1
|
||||
}
|
||||
CRDTOperationType::Shared(shared_op) => {
|
||||
let kind = match &shared_op.data {
|
||||
SharedOperationData::Create(_) => "c",
|
||||
|
@ -200,23 +170,29 @@ impl SyncManager {
|
|||
|
||||
match ModelSyncData::from_op(op.typ.clone()).unwrap() {
|
||||
ModelSyncData::FilePath(id, shared_op) => match shared_op {
|
||||
SharedOperationData::Create(SharedOperationCreateData::Unique(data)) => {
|
||||
SharedOperationData::Create(data) => {
|
||||
let data: Vec<_> = data
|
||||
.into_iter()
|
||||
.flat_map(|(k, v)| file_path::SetParam::deserialize(&k, v))
|
||||
.collect();
|
||||
|
||||
db.file_path()
|
||||
.create(
|
||||
id.pub_id,
|
||||
data.into_iter()
|
||||
.flat_map(|(k, v)| file_path::SetParam::deserialize(&k, v))
|
||||
.collect(),
|
||||
.upsert(
|
||||
file_path::pub_id::equals(id.pub_id.clone()),
|
||||
file_path::create(id.pub_id, data.clone()),
|
||||
data,
|
||||
)
|
||||
.exec()
|
||||
.await?;
|
||||
}
|
||||
SharedOperationData::Update { field, value } => {
|
||||
self.db
|
||||
.file_path()
|
||||
.update(
|
||||
file_path::pub_id::equals(id.pub_id),
|
||||
vec![file_path::SetParam::deserialize(&field, value).unwrap()],
|
||||
let data = vec![file_path::SetParam::deserialize(&field, value).unwrap()];
|
||||
|
||||
db.file_path()
|
||||
.upsert(
|
||||
file_path::pub_id::equals(id.pub_id.clone()),
|
||||
file_path::create(id.pub_id, data.clone()),
|
||||
data,
|
||||
)
|
||||
.exec()
|
||||
.await?;
|
||||
|
@ -224,13 +200,29 @@ impl SyncManager {
|
|||
_ => todo!(),
|
||||
},
|
||||
ModelSyncData::Location(id, shared_op) => match shared_op {
|
||||
SharedOperationData::Create(SharedOperationCreateData::Unique(data)) => {
|
||||
SharedOperationData::Create(data) => {
|
||||
let data: Vec<_> = data
|
||||
.into_iter()
|
||||
.flat_map(|(k, v)| location::SetParam::deserialize(&k, v))
|
||||
.collect();
|
||||
|
||||
db.location()
|
||||
.create(
|
||||
id.pub_id,
|
||||
data.into_iter()
|
||||
.flat_map(|(k, v)| location::SetParam::deserialize(&k, v))
|
||||
.collect(),
|
||||
.upsert(
|
||||
location::pub_id::equals(id.pub_id.clone()),
|
||||
location::create(id.pub_id, data.clone()),
|
||||
data,
|
||||
)
|
||||
.exec()
|
||||
.await?;
|
||||
}
|
||||
SharedOperationData::Update { field, value } => {
|
||||
let data = vec![location::SetParam::deserialize(&field, value).unwrap()];
|
||||
|
||||
db.location()
|
||||
.upsert(
|
||||
location::pub_id::equals(id.pub_id.clone()),
|
||||
location::create(id.pub_id, data.clone()),
|
||||
data,
|
||||
)
|
||||
.exec()
|
||||
.await?;
|
||||
|
@ -238,22 +230,29 @@ impl SyncManager {
|
|||
_ => todo!(),
|
||||
},
|
||||
ModelSyncData::Object(id, shared_op) => match shared_op {
|
||||
SharedOperationData::Create(_) => {
|
||||
SharedOperationData::Create(data) => {
|
||||
let data: Vec<_> = data
|
||||
.into_iter()
|
||||
.flat_map(|(k, v)| object::SetParam::deserialize(&k, v))
|
||||
.collect();
|
||||
|
||||
db.object()
|
||||
.upsert(
|
||||
object::pub_id::equals(id.pub_id.clone()),
|
||||
object::create(id.pub_id, vec![]),
|
||||
vec![],
|
||||
data,
|
||||
)
|
||||
.exec()
|
||||
.await
|
||||
.ok();
|
||||
.await?;
|
||||
}
|
||||
SharedOperationData::Update { field, value } => {
|
||||
let data = vec![object::SetParam::deserialize(&field, value).unwrap()];
|
||||
|
||||
db.object()
|
||||
.update(
|
||||
object::pub_id::equals(id.pub_id),
|
||||
vec![object::SetParam::deserialize(&field, value).unwrap()],
|
||||
.upsert(
|
||||
object::pub_id::equals(id.pub_id.clone()),
|
||||
object::create(id.pub_id, data.clone()),
|
||||
data,
|
||||
)
|
||||
.exec()
|
||||
.await?;
|
||||
|
@ -261,28 +260,29 @@ impl SyncManager {
|
|||
_ => todo!(),
|
||||
},
|
||||
ModelSyncData::Tag(id, shared_op) => match shared_op {
|
||||
SharedOperationData::Create(create_data) => match create_data {
|
||||
SharedOperationCreateData::Unique(create_data) => {
|
||||
db.tag()
|
||||
.create(
|
||||
id.pub_id,
|
||||
create_data
|
||||
.into_iter()
|
||||
.flat_map(|(field, value)| {
|
||||
tag::SetParam::deserialize(&field, value)
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
.exec()
|
||||
.await?;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
SharedOperationData::Update { field, value } => {
|
||||
SharedOperationData::Create(data) => {
|
||||
let data: Vec<_> = data
|
||||
.into_iter()
|
||||
.flat_map(|(field, value)| tag::SetParam::deserialize(&field, value))
|
||||
.collect();
|
||||
|
||||
db.tag()
|
||||
.update(
|
||||
tag::pub_id::equals(id.pub_id),
|
||||
vec![tag::SetParam::deserialize(&field, value).unwrap()],
|
||||
.upsert(
|
||||
tag::pub_id::equals(id.pub_id.clone()),
|
||||
tag::create(id.pub_id, data.clone()),
|
||||
data,
|
||||
)
|
||||
.exec()
|
||||
.await?;
|
||||
}
|
||||
SharedOperationData::Update { field, value } => {
|
||||
let data = vec![tag::SetParam::deserialize(&field, value).unwrap()];
|
||||
|
||||
db.tag()
|
||||
.upsert(
|
||||
tag::pub_id::equals(id.pub_id.clone()),
|
||||
tag::create(id.pub_id, data.clone()),
|
||||
data,
|
||||
)
|
||||
.exec()
|
||||
.await?;
|
||||
|
@ -335,91 +335,6 @@ impl SyncManager {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn owned_create<
|
||||
const SIZE: usize,
|
||||
TSyncId: SyncId<ModelTypes = TModel>,
|
||||
TModel: SyncType<Marker = OwnedSyncType>,
|
||||
>(
|
||||
&self,
|
||||
id: TSyncId,
|
||||
values: [(&'static str, Value); SIZE],
|
||||
) -> CRDTOperation {
|
||||
self.new_op(CRDTOperationType::Owned(OwnedOperation {
|
||||
model: TModel::MODEL.to_string(),
|
||||
items: [(id, values)]
|
||||
.into_iter()
|
||||
.map(|(id, data)| OwnedOperationItem {
|
||||
id: json!(id),
|
||||
data: OwnedOperationData::Create(
|
||||
data.into_iter().map(|(k, v)| (k.to_string(), v)).collect(),
|
||||
),
|
||||
})
|
||||
.collect(),
|
||||
}))
|
||||
}
|
||||
pub fn owned_create_many<
|
||||
const SIZE: usize,
|
||||
TSyncId: SyncId<ModelTypes = TModel>,
|
||||
TModel: SyncType<Marker = OwnedSyncType>,
|
||||
>(
|
||||
&self,
|
||||
data: impl IntoIterator<Item = (TSyncId, [(&'static str, Value); SIZE])>,
|
||||
skip_duplicates: bool,
|
||||
) -> CRDTOperation {
|
||||
self.new_op(CRDTOperationType::Owned(OwnedOperation {
|
||||
model: TModel::MODEL.to_string(),
|
||||
items: vec![OwnedOperationItem {
|
||||
id: Value::Null,
|
||||
data: OwnedOperationData::CreateMany {
|
||||
values: data
|
||||
.into_iter()
|
||||
.map(|(id, data)| {
|
||||
(
|
||||
json!(id),
|
||||
data.into_iter().map(|(k, v)| (k.to_string(), v)).collect(),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
skip_duplicates,
|
||||
},
|
||||
}],
|
||||
}))
|
||||
}
|
||||
pub fn owned_update<
|
||||
TSyncId: SyncId<ModelTypes = TModel>,
|
||||
TModel: SyncType<Marker = OwnedSyncType>,
|
||||
>(
|
||||
&self,
|
||||
id: TSyncId,
|
||||
values: impl IntoIterator<Item = (&'static str, Value)>,
|
||||
) -> CRDTOperation {
|
||||
self.new_op(CRDTOperationType::Owned(OwnedOperation {
|
||||
model: TModel::MODEL.to_string(),
|
||||
items: [(id, values)]
|
||||
.into_iter()
|
||||
.map(|(id, data)| OwnedOperationItem {
|
||||
id: json!(id),
|
||||
data: OwnedOperationData::Update(
|
||||
data.into_iter().map(|(k, v)| (k.to_string(), v)).collect(),
|
||||
),
|
||||
})
|
||||
.collect(),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn shared_create<
|
||||
TSyncId: SyncId<ModelTypes = TModel>,
|
||||
TModel: SyncType<Marker = SharedSyncType>,
|
||||
>(
|
||||
&self,
|
||||
id: TSyncId,
|
||||
) -> CRDTOperation {
|
||||
self.new_op(CRDTOperationType::Shared(SharedOperation {
|
||||
model: TModel::MODEL.to_string(),
|
||||
record_id: json!(id),
|
||||
data: SharedOperationData::Create(SharedOperationCreateData::Atomic),
|
||||
}))
|
||||
}
|
||||
pub fn unique_shared_create<
|
||||
TSyncId: SyncId<ModelTypes = TModel>,
|
||||
TModel: SyncType<Marker = SharedSyncType>,
|
||||
|
@ -431,12 +346,12 @@ impl SyncManager {
|
|||
self.new_op(CRDTOperationType::Shared(SharedOperation {
|
||||
model: TModel::MODEL.to_string(),
|
||||
record_id: json!(id),
|
||||
data: SharedOperationData::Create(SharedOperationCreateData::Unique(
|
||||
data: SharedOperationData::Create(
|
||||
values
|
||||
.into_iter()
|
||||
.map(|(name, value)| (name.to_string(), value))
|
||||
.collect(),
|
||||
)),
|
||||
),
|
||||
}))
|
||||
}
|
||||
pub fn shared_update<
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
pub fn r#struct(datamodel: &dml::Datamodel) -> TokenStream {
|
||||
let model_action_fns = datamodel.models.iter().map(|model| {
|
||||
let model_name_snake = snake_ident(&model.name);
|
||||
let model_actions_struct = quote!(super::#model_name_snake::Actions);
|
||||
|
||||
quote! {
|
||||
pub fn #model_name_snake(&self) -> #model_actions_struct {
|
||||
#model_actions_struct { client: self }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
pub struct PrismaCRDTClient {
|
||||
pub(super) client: #PRISMA::PrismaClient,
|
||||
pub node_id: Vec<u8>,
|
||||
operation_sender: #MPSC::Sender<#SYNC::CRDTOperation>
|
||||
}
|
||||
|
||||
impl PrismaCRDTClient {
|
||||
pub(super) fn _new(
|
||||
client: #PRISMA::PrismaClient,
|
||||
node_id: Vec<u8>,
|
||||
operation_sender: #MPSC::Sender<#SYNC::CRDTOperation>
|
||||
) -> Self {
|
||||
Self {
|
||||
client,
|
||||
node_id,
|
||||
operation_sender,
|
||||
}
|
||||
}
|
||||
|
||||
#(#model_action_fns)*
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,186 +0,0 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Field<'a> {
|
||||
pub prisma: &'a dml::Field,
|
||||
pub model: &'a str,
|
||||
pub typ: FieldType<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Field<'a> {
|
||||
pub fn new(field: &'a dml::Field, model: &'a str, datamodel: &'a dml::Datamodel) -> Field<'a> {
|
||||
let typ = FieldType::new(field, model, datamodel);
|
||||
|
||||
Self {
|
||||
prisma: field,
|
||||
model,
|
||||
typ,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the token representation of the field's type,
|
||||
/// accounting for a sync ID reference if it is a field
|
||||
/// of a relation
|
||||
pub fn crdt_type_tokens(&self, datamodel: &Datamodel) -> TokenStream {
|
||||
let relation_field_info = match &self.typ {
|
||||
FieldType::Scalar {
|
||||
relation_field_info,
|
||||
} => relation_field_info,
|
||||
_ => unreachable!("Cannot get CRDT type for non-scalar field"),
|
||||
};
|
||||
|
||||
match relation_field_info.as_ref() {
|
||||
Some(relation_field_info) => {
|
||||
let relation_model = datamodel
|
||||
.model(relation_field_info.referenced_model)
|
||||
.unwrap();
|
||||
|
||||
let sync_id_field =
|
||||
relation_model.sync_id_for_pk(relation_field_info.referenced_field);
|
||||
|
||||
match sync_id_field {
|
||||
Some(field) => {
|
||||
let relation_field_type = field.field_type().to_tokens();
|
||||
|
||||
match self.arity() {
|
||||
dml::FieldArity::Required => relation_field_type,
|
||||
dml::FieldArity::Optional => quote!(Option<#relation_field_type>),
|
||||
dml::FieldArity::List => quote!(Vec<#relation_field_type>),
|
||||
}
|
||||
}
|
||||
None => self.type_tokens(),
|
||||
}
|
||||
}
|
||||
None => datamodel
|
||||
.model(self.model)
|
||||
.unwrap()
|
||||
.sync_id_for_pk(self.name())
|
||||
.unwrap_or(self)
|
||||
.type_tokens(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deref for Field<'a> {
|
||||
type Target = dml::Field;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.prisma
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FieldType<'a> {
|
||||
Scalar {
|
||||
/// The relation field that this scalar field is a part of.
|
||||
relation_field_info: Option<RelationFieldInfo<'a>>,
|
||||
},
|
||||
Relation {
|
||||
relation_info: RelationInfo<'a>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> FieldType<'a> {
|
||||
fn new(field: &'a dml::Field, model: &str, datamodel: &'a dml::Datamodel) -> Self {
|
||||
match field.field_type() {
|
||||
dml::FieldType::Scalar(_, _, _) => FieldType::Scalar {
|
||||
relation_field_info: {
|
||||
datamodel
|
||||
.find_model(model)
|
||||
.unwrap()
|
||||
.fields()
|
||||
.find_map(|relation_field| {
|
||||
relation_field
|
||||
.as_relation_field()
|
||||
.and_then(|relation_field_data| {
|
||||
relation_field_data
|
||||
.relation_info
|
||||
.fields
|
||||
.iter()
|
||||
.position(|rf_name| rf_name == field.name())
|
||||
.map(|pos| (relation_field_data, pos))
|
||||
})
|
||||
.and_then(|(relation_field_data, i)| {
|
||||
datamodel
|
||||
.models()
|
||||
.find(|relation_model| {
|
||||
relation_model.name
|
||||
== relation_field_data.relation_info.to
|
||||
})
|
||||
.and_then(|relation_model| {
|
||||
relation_model
|
||||
.fields()
|
||||
.find(|referenced_field| {
|
||||
referenced_field.name()
|
||||
== relation_field_data
|
||||
.relation_info
|
||||
.references[i]
|
||||
})
|
||||
.map(|f| (relation_model, f))
|
||||
})
|
||||
})
|
||||
.map(|(ref_model, ref_field)| {
|
||||
(relation_field.name(), &ref_model.name, ref_field.name())
|
||||
})
|
||||
})
|
||||
.map(|(rel, ref_model, ref_field)| {
|
||||
RelationFieldInfo::new(rel, ref_model, ref_field)
|
||||
})
|
||||
},
|
||||
},
|
||||
dml::FieldType::Relation(_) => FieldType::Relation {
|
||||
relation_info: {
|
||||
field
|
||||
.as_relation_field()
|
||||
.filter(|rf| !OPERATION_MODELS.contains(&rf.relation_info.to.as_str()))
|
||||
.map(|rf| {
|
||||
RelationInfo::new(
|
||||
&rf.relation_info.to,
|
||||
&rf.relation_info.fields,
|
||||
&rf.relation_info.references,
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
},
|
||||
},
|
||||
t => unimplemented!("Unsupported field type: {:?}", t),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RelationFieldInfo<'a> {
|
||||
/// Field on the same model that represents the relation
|
||||
pub relation: &'a str,
|
||||
pub referenced_model: &'a str,
|
||||
/// Scalar field on the referenced model that matches the scalar on the same model
|
||||
pub referenced_field: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> RelationFieldInfo<'a> {
|
||||
pub fn new(relation: &'a str, referenced_model: &'a str, referenced_field: &'a str) -> Self {
|
||||
Self {
|
||||
relation,
|
||||
referenced_model,
|
||||
referenced_field,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RelationInfo<'a> {
|
||||
pub to: &'a str,
|
||||
pub fields: &'a Vec<String>,
|
||||
pub references: &'a Vec<String>,
|
||||
}
|
||||
|
||||
impl<'a> RelationInfo<'a> {
|
||||
pub fn new(to: &'a str, fields: &'a Vec<String>, references: &'a Vec<String>) -> Self {
|
||||
Self {
|
||||
to,
|
||||
fields,
|
||||
references,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
mod field;
|
||||
mod model;
|
||||
|
||||
pub use field::*;
|
||||
pub use model::*;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
pub const OPERATION_MODELS: &[&str] = &["OwnedOperation", "SharedOperation", "RelationOperation"];
|
||||
|
||||
pub struct Datamodel<'a> {
|
||||
pub prisma: &'a dml::Datamodel,
|
||||
pub models: Vec<Model<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> Datamodel<'a> {
|
||||
pub fn model(&self, name: &str) -> Option<&'a Model> {
|
||||
self.models.iter().find(|m| m.name == name)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a dml::Datamodel> for Datamodel<'a> {
|
||||
type Error = String;
|
||||
fn try_from(datamodel: &'a dml::Datamodel) -> Result<Self, Self::Error> {
|
||||
let models = datamodel
|
||||
.models
|
||||
.iter()
|
||||
.filter(|m| !OPERATION_MODELS.contains(&m.name.as_str()))
|
||||
.map(|m| Model::new(m, datamodel))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let datamodel = Self {
|
||||
prisma: datamodel,
|
||||
models,
|
||||
};
|
||||
|
||||
Ok(datamodel)
|
||||
}
|
||||
}
|
|
@ -1,314 +0,0 @@
|
|||
use std::{ops::Deref, str::FromStr};
|
||||
|
||||
use crate::attribute::{Attribute, AttributeFieldValue};
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Model<'a> {
|
||||
pub prisma: &'a dml::Model,
|
||||
pub typ: ModelType,
|
||||
pub fields: Vec<Field<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> Model<'a> {
|
||||
pub fn new(model: &'a dml::Model, datamodel: &'a dml::Datamodel) -> Result<Self, String> {
|
||||
let crdt_attribute = model
|
||||
.documentation
|
||||
.as_ref()
|
||||
.map(Attribute::parse)
|
||||
.map(Result::unwrap)
|
||||
.unwrap();
|
||||
|
||||
let fields = model
|
||||
.fields()
|
||||
.filter(|f| {
|
||||
f.as_relation_field()
|
||||
.filter(|rf| OPERATION_MODELS.contains(&rf.relation_info.to.as_str()))
|
||||
.is_none()
|
||||
})
|
||||
.map(|f| Field::new(f, &model.name, datamodel))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let typ = ModelType::from_attribute(&crdt_attribute, &fields, model)?;
|
||||
|
||||
let model = Self {
|
||||
prisma: model,
|
||||
typ,
|
||||
fields,
|
||||
};
|
||||
|
||||
Ok(model)
|
||||
}
|
||||
|
||||
pub fn field(&self, name: &str) -> Option<&Field<'a>> {
|
||||
self.fields.iter().find(|f| f.name() == name)
|
||||
}
|
||||
|
||||
pub fn is_sync_id(&self, field: &str) -> bool {
|
||||
match &self.typ {
|
||||
ModelType::Local { id } => id.is_sync_id(field),
|
||||
ModelType::Owned { id, .. } => id.is_sync_id(field),
|
||||
ModelType::Shared { id, .. } => id.is_sync_id(field),
|
||||
ModelType::Relation { item, group } => {
|
||||
item.is_sync_id(field) || group.is_sync_id(field)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_pk(&self, field: &str) -> bool {
|
||||
self.primary_key
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.fields
|
||||
.iter()
|
||||
.any(|f| f.name == field)
|
||||
}
|
||||
|
||||
pub fn sync_id_for_pk(&self, primary_key: &str) -> Option<&Field<'a>> {
|
||||
let pk_index = self
|
||||
.primary_key
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.fields
|
||||
.iter()
|
||||
.position(|f| f.name == primary_key);
|
||||
|
||||
pk_index
|
||||
.and_then(|pk_index| match &self.typ {
|
||||
ModelType::Local { id } => id.at_index(pk_index),
|
||||
ModelType::Owned { id, .. } => id.at_index(pk_index),
|
||||
ModelType::Shared { id, .. } => id.at_index(pk_index),
|
||||
ModelType::Relation { item, group } => {
|
||||
item.at_index(0).or_else(|| group.at_index(0))
|
||||
}
|
||||
})
|
||||
.and_then(|f| self.field(f))
|
||||
}
|
||||
|
||||
/// Gets the scalar sync id fields for a model, along with the (possibly) foreign field
|
||||
/// that their types should be resolved from.
|
||||
///
|
||||
/// For example, a scalar field will have no difference between the first and second element.
|
||||
/// A relation, however, will result in the first element being the model's scalar field,
|
||||
/// and the second element being the foreign scalar field. It is important to note that these foreign
|
||||
/// fields could be primary keys that map to sync ids, and this should be checked.
|
||||
pub fn scalar_sync_id_fields(
|
||||
&'a self,
|
||||
datamodel: &'a Datamodel,
|
||||
) -> impl Iterator<Item = (&'a Field, &'a Field)> {
|
||||
self.fields
|
||||
.iter()
|
||||
.filter(|f| self.is_sync_id(f.name()))
|
||||
.flat_map(|field| match &field.typ {
|
||||
FieldType::Scalar { .. } => {
|
||||
vec![(field, field)]
|
||||
}
|
||||
FieldType::Relation { relation_info } => relation_info
|
||||
.fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, field)| {
|
||||
let relation_model = datamodel.model(relation_info.to).unwrap();
|
||||
// Scalar field on the relation model. Could be a local id,
|
||||
// so crdt type must be used
|
||||
let referenced_field =
|
||||
relation_model.field(&relation_info.references[i]).unwrap();
|
||||
|
||||
(self.field(field).unwrap(), referenced_field)
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deref for Model<'a> {
|
||||
type Target = dml::Model;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.prisma
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ModelType {
|
||||
Local {
|
||||
id: SyncIDMapping,
|
||||
},
|
||||
Owned {
|
||||
owner: String,
|
||||
id: SyncIDMapping,
|
||||
},
|
||||
Shared {
|
||||
id: SyncIDMapping,
|
||||
create: SharedCreateType,
|
||||
},
|
||||
Relation {
|
||||
item: SyncIDMapping,
|
||||
group: SyncIDMapping,
|
||||
},
|
||||
}
|
||||
|
||||
impl ModelType {
|
||||
pub fn from_attribute(
|
||||
attribute: &Attribute,
|
||||
fields: &[Field],
|
||||
model: &dml::Model,
|
||||
) -> Result<Self, String> {
|
||||
let ret = match attribute.name {
|
||||
"local" => {
|
||||
let id = SyncIDMapping::from_attribute(attribute.field("id"), fields, model)?;
|
||||
|
||||
ModelType::Local { id }
|
||||
}
|
||||
"owned" => {
|
||||
let id = SyncIDMapping::from_attribute(attribute.field("id"), fields, model)?;
|
||||
|
||||
let owner = attribute
|
||||
.field("owner")
|
||||
.ok_or_else(|| "Missing owner field".to_string())
|
||||
.map(|owner| owner.as_single().expect("Owner field must be a string"))
|
||||
.and_then(|owner| {
|
||||
fields
|
||||
.iter()
|
||||
.find(|f| f.name() == owner)
|
||||
.map(|f| f.name().to_string())
|
||||
.ok_or(format!("Unknown owner field {}", owner))
|
||||
})?;
|
||||
|
||||
ModelType::Owned { id, owner }
|
||||
}
|
||||
"shared" => {
|
||||
let id = SyncIDMapping::from_attribute(attribute.field("id"), fields, model)?;
|
||||
|
||||
let create = attribute
|
||||
.field("create")
|
||||
.map(|create| create.as_single().expect("create field must be a string"))
|
||||
.map(SharedCreateType::from_str)
|
||||
.unwrap_or(Ok(SharedCreateType::Unique))?;
|
||||
|
||||
ModelType::Shared { id, create }
|
||||
}
|
||||
"relation" => {
|
||||
let item = SyncIDMapping::from_attribute(
|
||||
Some(
|
||||
attribute
|
||||
.field("item")
|
||||
.expect("@relation attribute missing `item` field"),
|
||||
),
|
||||
fields,
|
||||
model,
|
||||
)?;
|
||||
let group = SyncIDMapping::from_attribute(
|
||||
Some(
|
||||
attribute
|
||||
.field("group")
|
||||
.expect("@relation attribute missing `group` field"),
|
||||
),
|
||||
fields,
|
||||
model,
|
||||
)?;
|
||||
|
||||
ModelType::Relation { item, group }
|
||||
}
|
||||
name => Err(format!("Invalid attribute type {name}"))?,
|
||||
};
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SyncIDMapping {
|
||||
Single(String),
|
||||
Compound(Vec<String>),
|
||||
}
|
||||
|
||||
impl SyncIDMapping {
|
||||
pub fn from_attribute(
|
||||
attr_value: Option<&AttributeFieldValue>,
|
||||
fields: &[Field],
|
||||
model: &dml::Model,
|
||||
) -> Result<Self, String> {
|
||||
let primary_key = model
|
||||
.primary_key
|
||||
.as_ref()
|
||||
.ok_or(format!("Model {} has no primary key", model.name))?;
|
||||
|
||||
attr_value
|
||||
.map(|attr_value| match attr_value {
|
||||
AttributeFieldValue::Single(field) => {
|
||||
fields
|
||||
.iter()
|
||||
.find(|f| f.name() == *field)
|
||||
.ok_or(format!("Unknown field {}", field))?;
|
||||
|
||||
Ok(SyncIDMapping::Single(field.to_string()))
|
||||
}
|
||||
AttributeFieldValue::List(field_list) => {
|
||||
if primary_key.fields.len() != field_list.len() {
|
||||
return Err(format!(
|
||||
"Sync ID for model {} has inconsistent number of fields",
|
||||
model.name,
|
||||
));
|
||||
}
|
||||
|
||||
field_list
|
||||
.iter()
|
||||
.map(|name| {
|
||||
fields
|
||||
.iter()
|
||||
.find(|f| f.name() == *name)
|
||||
.map(|f| f.name().to_string())
|
||||
})
|
||||
.collect::<Option<_>>()
|
||||
.map(SyncIDMapping::Compound)
|
||||
.ok_or(format!("Invalid sync ID for model {}", model.name))
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
Ok(match primary_key.fields.len() {
|
||||
1 => SyncIDMapping::Single(primary_key.fields[0].name.to_string()),
|
||||
_ => SyncIDMapping::Compound(
|
||||
primary_key
|
||||
.fields
|
||||
.iter()
|
||||
.map(|f| f.name.to_string())
|
||||
.collect(),
|
||||
),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_sync_id(&self, field: &str) -> bool {
|
||||
match self {
|
||||
Self::Single(v) => field == v,
|
||||
Self::Compound(mappings) => mappings.iter().any(|v| field == v),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn at_index(&self, i: usize) -> Option<&str> {
|
||||
match self {
|
||||
Self::Single(v) => Some(v),
|
||||
Self::Compound(mappings) => mappings.get(i).map(|v| v.as_str()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SharedCreateType {
|
||||
Unique,
|
||||
Atomic,
|
||||
}
|
||||
|
||||
impl FromStr for SharedCreateType {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let ret = match s {
|
||||
"Unique" => SharedCreateType::Unique,
|
||||
"Atomic" => SharedCreateType::Atomic,
|
||||
s => Err(format!("Invalid create type {}", s))?,
|
||||
};
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
|
@ -1,182 +0,0 @@
|
|||
use crate::generator::prelude::*;
|
||||
|
||||
/// Generates the `_create_operation` function for the CRDT client
|
||||
fn create_operation_fn() -> TokenStream {
|
||||
quote! {
|
||||
pub async fn _create_operation(&self, typ: ::prisma_crdt::CRDTOperationType) {
|
||||
let timestamp = ::uhlc::NTP64(0); // TODO: actual timestamps
|
||||
|
||||
let timestamp_bytes = vec![0];
|
||||
|
||||
match &typ {
|
||||
::prisma_crdt::CRDTOperationType::Shared(::prisma_crdt::SharedOperation {
|
||||
record_id,
|
||||
model,
|
||||
data,
|
||||
}) => {
|
||||
let (kind, data) = match data {
|
||||
::prisma_crdt::SharedOperationData::Create(typ) => {
|
||||
("c".to_string(), ::serde_json::to_vec(typ).unwrap())
|
||||
}
|
||||
::prisma_crdt::SharedOperationData::Update { field, value } => {
|
||||
("u".to_string() + field, ::serde_json::to_vec(value).unwrap())
|
||||
}
|
||||
::prisma_crdt::SharedOperationData::Delete => ("d".to_string(), vec![]),
|
||||
};
|
||||
|
||||
self.client
|
||||
.shared_operation()
|
||||
.create(
|
||||
timestamp_bytes,
|
||||
::serde_json::to_vec(&record_id).unwrap(),
|
||||
kind,
|
||||
model.to_string(),
|
||||
data,
|
||||
crate::prisma::node::local_id::equals(self.node_local_id),
|
||||
vec![],
|
||||
)
|
||||
.exec()
|
||||
.await;
|
||||
}
|
||||
::prisma_crdt::CRDTOperationType::Owned(op) => {
|
||||
self.client
|
||||
.owned_operation()
|
||||
.create(
|
||||
timestamp_bytes,
|
||||
::serde_json::to_vec(op).unwrap(),
|
||||
crate::prisma::node::local_id::equals(self.node_local_id),
|
||||
vec![],
|
||||
)
|
||||
.exec()
|
||||
.await;
|
||||
}
|
||||
::prisma_crdt::CRDTOperationType::Relation(::prisma_crdt::RelationOperation {
|
||||
relation,
|
||||
relation_item,
|
||||
relation_group,
|
||||
data,
|
||||
}) => {
|
||||
let (kind, data) = match data {
|
||||
::prisma_crdt::RelationOperationData::Create => ("c".to_string(), vec![]),
|
||||
::prisma_crdt::RelationOperationData::Update { field, value } => {
|
||||
("u".to_string() + field, ::serde_json::to_vec(value).unwrap())
|
||||
}
|
||||
::prisma_crdt::RelationOperationData::Delete => ("d".to_string(), vec![]),
|
||||
};
|
||||
|
||||
self.client
|
||||
.relation_operation()
|
||||
.create(
|
||||
timestamp_bytes,
|
||||
relation.to_string(),
|
||||
::serde_json::to_vec(&relation_item).unwrap(),
|
||||
::serde_json::to_vec(&relation_group).unwrap(),
|
||||
kind,
|
||||
data,
|
||||
crate::prisma::node::local_id::equals(self.node_local_id),
|
||||
vec![],
|
||||
)
|
||||
.exec()
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
let op = ::prisma_crdt::CRDTOperation::new(self.node_id.clone(), timestamp, typ);
|
||||
|
||||
self.operation_sender.send(op).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates action getters for each model for the CRDT Client
|
||||
fn actions_accessors(datamodel: DatamodelRef) -> Vec<TokenStream> {
|
||||
datamodel
|
||||
.models
|
||||
.iter()
|
||||
.map(|model| {
|
||||
let name_snake = snake_ident(&model.name);
|
||||
|
||||
match &model.typ {
|
||||
ModelType::Local { .. } => quote! {
|
||||
pub fn #name_snake(&self) -> crate::prisma::#name_snake::Actions {
|
||||
self.client.#name_snake()
|
||||
}
|
||||
},
|
||||
_ => quote! {
|
||||
pub fn #name_snake(&self) -> super::#name_snake::Actions {
|
||||
super::#name_snake::Actions::new(self)
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Generates the `_prisma` module and its `PrismaCRDTClient` struct
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```
|
||||
/// mod _prisma {
|
||||
/// pub struct PrismaCRDTClient {
|
||||
/// pub(super) client: crate::prisma::PrismaClient,
|
||||
/// pub node_id: Vec<u8>,
|
||||
/// pub node_local_id: i32,
|
||||
/// operation_sender: ::tokio::sync::mpsc::Sender<::prisma_crdt::CRDTOperation>
|
||||
/// }
|
||||
///
|
||||
/// impl PrismaCRDTClient {
|
||||
/// pub(super) fn _new(
|
||||
/// client: crate::prisma::PrismaClient,
|
||||
/// (node_id, node_local_id): Vec<u8, i32>,
|
||||
/// operation_sender: ::tokio::sync::mpsc::Sender<::prisma_crdt::CRDTOperation>
|
||||
/// ) -> Self {
|
||||
/// Self {
|
||||
/// client,
|
||||
/// operation_sender,
|
||||
/// node_id,
|
||||
/// node_local_id
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// pub async fn _create_operation(..) { .. }
|
||||
///
|
||||
/// ..
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn generate(datamodel: DatamodelRef) -> TokenStream {
|
||||
let create_operation_fn = create_operation_fn();
|
||||
|
||||
let actions_accessors = actions_accessors(datamodel);
|
||||
|
||||
quote! {
|
||||
mod _prisma {
|
||||
pub struct PrismaCRDTClient {
|
||||
pub(super) client: crate::prisma::PrismaClient,
|
||||
pub node_id: Vec<u8>,
|
||||
pub node_local_id: i32,
|
||||
operation_sender: ::tokio::sync::mpsc::Sender<::prisma_crdt::CRDTOperation>,
|
||||
}
|
||||
|
||||
impl PrismaCRDTClient {
|
||||
pub(super) fn _new(
|
||||
client: crate::prisma::PrismaClient,
|
||||
(node_id, node_local_id): (Vec<u8>, i32),
|
||||
operation_sender: ::tokio::sync::mpsc::Sender<::prisma_crdt::CRDTOperation>,
|
||||
) -> Self {
|
||||
Self {
|
||||
client,
|
||||
operation_sender,
|
||||
node_id,
|
||||
node_local_id,
|
||||
}
|
||||
}
|
||||
|
||||
#create_operation_fn
|
||||
|
||||
#(#actions_accessors)*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
mod client;
|
||||
mod model;
|
||||
|
||||
use super::prelude::*;
|
||||
use super::*;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct PrismaCRDTGenerator {}
|
||||
|
||||
impl PrismaGenerator for PrismaCRDTGenerator {
|
||||
const NAME: &'static str = "Prisma CRDT Generator";
|
||||
const DEFAULT_OUTPUT: &'static str = "./prisma-crdt.rs";
|
||||
|
||||
fn generate(self, args: GenerateArgs) -> String {
|
||||
let datamodel =
|
||||
datamodel::Datamodel::try_from(&args.dml).expect("Failed to construct datamodel");
|
||||
let datamodel_ref = prelude::DatamodelRef(&datamodel);
|
||||
|
||||
let header = quote! {
|
||||
#![allow(clippy::all)]
|
||||
|
||||
pub async fn new_client(
|
||||
prisma_client: crate::prisma::PrismaClient,
|
||||
node_id: Vec<u8>,
|
||||
node_local_id: i32
|
||||
) -> (
|
||||
_prisma::PrismaCRDTClient,
|
||||
::tokio::sync::mpsc::Receiver<::prisma_crdt::CRDTOperation>,
|
||||
) {
|
||||
let (tx, rx) = ::tokio::sync::mpsc::channel(64);
|
||||
|
||||
let crdt_client = _prisma::PrismaCRDTClient::_new(prisma_client, (node_id, node_local_id), tx);
|
||||
(crdt_client, rx)
|
||||
}
|
||||
pub use _prisma::*;
|
||||
};
|
||||
|
||||
let client = client::generate(datamodel_ref);
|
||||
|
||||
let models = datamodel
|
||||
.models
|
||||
.iter()
|
||||
.map(|model| model::generate(ModelRef::new(model, datamodel_ref)));
|
||||
|
||||
let output = quote! {
|
||||
#header
|
||||
|
||||
#(#models)*
|
||||
|
||||
#client
|
||||
};
|
||||
|
||||
output.to_string()
|
||||
}
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
use crate::generator::prelude::*;
|
||||
|
||||
use super::create;
|
||||
|
||||
/// Generates struct definition for a model's `Actions` struct
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```
|
||||
/// pub struct Actions<'a> {
|
||||
/// client: &'a super::_prisma::PrismaCRDTClient
|
||||
/// }
|
||||
///
|
||||
/// impl<'a> Actions<'a> {
|
||||
/// pub(super) fn new(client: &'a super::_prisma::PrismaCRDTClient) -> Self {
|
||||
/// Self { client }
|
||||
/// }
|
||||
///
|
||||
/// pub fn create(..) {
|
||||
/// ..
|
||||
/// }
|
||||
///
|
||||
/// pub fn find_unique(
|
||||
/// self,
|
||||
/// param: crate::prisma::#model::UniqueWhereParam
|
||||
/// ) -> crate::prisma::#model::FindUnique<'a> {
|
||||
/// self.client.client.#model().find_unique(param)
|
||||
/// }
|
||||
///
|
||||
/// pub fn find_many(
|
||||
/// self,
|
||||
/// params: Vec<crate::prisma::#model::WhereParam>
|
||||
/// ) -> crate::prisma::#model::FindMany<'a> {
|
||||
/// self.client.client.#model().find_many(params)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn definition(model: ModelRef) -> TokenStream {
|
||||
let name = snake_ident(&model.name);
|
||||
|
||||
let create_fn = create::action_method(model);
|
||||
|
||||
quote! {
|
||||
pub struct Actions<'a> {
|
||||
client: &'a super::_prisma::PrismaCRDTClient,
|
||||
}
|
||||
|
||||
impl<'a> Actions<'a> {
|
||||
pub(super) fn new(client: &'a super::_prisma::PrismaCRDTClient) -> Self {
|
||||
Self { client }
|
||||
}
|
||||
|
||||
#create_fn
|
||||
|
||||
pub fn find_unique(
|
||||
self,
|
||||
param: crate::prisma::#name::UniqueWhereParam,
|
||||
) -> crate::prisma::#name::FindUnique<'a> {
|
||||
self.client.client.#name().find_unique(param)
|
||||
}
|
||||
|
||||
pub fn find_many(
|
||||
self,
|
||||
params: Vec<crate::prisma::#name::WhereParam>,
|
||||
) -> crate::prisma::#name::FindMany<'a> {
|
||||
self.client.client.#name().find_many(params)
|
||||
}
|
||||
|
||||
pub fn update(
|
||||
self,
|
||||
_where: crate::prisma::#name::UniqueWhereParam,
|
||||
set_params: Vec<SetParam>,
|
||||
) -> Update<'a> {
|
||||
Update {
|
||||
client: self.client,
|
||||
where_param: _where,
|
||||
set_params,
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn delete(self, param: crate::prisma::#name::UniqueWhereParam) -> Delete<'a> {
|
||||
// Delete {
|
||||
// client: self.client,
|
||||
// r#where: param,
|
||||
// with_params: vec![],
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,186 +0,0 @@
|
|||
use crate::generator::prelude::*;
|
||||
|
||||
use super::{create_params, owned, relation, shared};
|
||||
|
||||
/// Generates a call to the underlying Prisma client's `create` method for
|
||||
/// the given model
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```
|
||||
/// self
|
||||
/// .crdt_client
|
||||
/// .client
|
||||
/// .user()
|
||||
/// .create(
|
||||
/// self.set_params.name.clone(),
|
||||
/// self.set_params.profile_id.clone),
|
||||
/// self.set_params._params.clone().into_iter().map(Into::into).collect()
|
||||
/// )
|
||||
/// .exec()
|
||||
/// .await?
|
||||
/// ```
|
||||
pub fn prisma_create(model: ModelRef) -> TokenStream {
|
||||
let model_name = snake_ident(&model.name);
|
||||
|
||||
let create_args = model
|
||||
.fields
|
||||
.iter()
|
||||
.filter(|f| {
|
||||
f.required_on_create()
|
||||
&& f.as_scalar_field()
|
||||
.map(|sf| !model.scalar_field_has_relation(sf))
|
||||
.unwrap_or(true)
|
||||
})
|
||||
.map(|field| {
|
||||
let field_name_snake = snake_ident(field.name());
|
||||
|
||||
match &field.typ {
|
||||
FieldType::Relation {relation_info} => {
|
||||
let relation_model_snake = snake_ident(relation_info.to);
|
||||
|
||||
if relation_info.fields.len() == 1 {
|
||||
let relation_field_snake = snake_ident(&relation_info.fields[0]);
|
||||
let referenced_field_snake = snake_ident(&relation_info.references[0]);
|
||||
|
||||
quote!(crate::prisma::#relation_model_snake::#referenced_field_snake::equals(self.set_params.#relation_field_snake.clone()))
|
||||
} else {
|
||||
todo!()
|
||||
}
|
||||
},
|
||||
_ => quote!(self.set_params.#field_name_snake.clone()),
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
self
|
||||
.crdt_client
|
||||
.client
|
||||
.#model_name()
|
||||
.create(
|
||||
#(#create_args,)*
|
||||
self.set_params._params.clone().into_iter().map(Into::into).collect(),
|
||||
)
|
||||
.exec()
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates the definition for a model's `Create` struct
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```
|
||||
/// pub struct Create<'a> {
|
||||
/// crdt_client: &'a super::_prisma::PrismaCRDTClient,
|
||||
/// set_params: CreateParams,
|
||||
/// with_params: Vec<crate::prisma::#model_name_snake::WithParam>
|
||||
/// }
|
||||
///
|
||||
/// impl<'a> Create<'a> {
|
||||
/// pub(super) fn new(
|
||||
/// crdt_client: &'a super::_prisma::PrismaCRDTClient,
|
||||
/// set_params: CreateParams,
|
||||
/// with_params: Vec<crate::prisma::#model_name_snake::WithParam>
|
||||
/// )
|
||||
///
|
||||
/// ..
|
||||
///
|
||||
/// pub async fn exec(
|
||||
/// self
|
||||
/// ) -> Result<crate::prisma::#model_name_snake::WithParam, crate::prisma::QueryError> {
|
||||
/// let res = self
|
||||
/// .crdt_client
|
||||
/// .client
|
||||
/// .#model_name_snake()
|
||||
/// .create(
|
||||
/// ..
|
||||
/// )
|
||||
/// .exec()
|
||||
/// .await?;
|
||||
///
|
||||
/// ..
|
||||
///
|
||||
/// Ok(res)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn struct_definition(model: ModelRef) -> TokenStream {
|
||||
let model_name_snake = snake_ident(&model.name);
|
||||
|
||||
let create_call = prisma_create(model);
|
||||
|
||||
let exec_body = match model.typ {
|
||||
ModelType::Owned { .. } => owned::create_exec_body(model),
|
||||
ModelType::Shared { .. } => shared::create_exec_body(model),
|
||||
ModelType::Relation { .. } => relation::create_exec_body(model),
|
||||
// SAFETY: Local models don't have method overrides
|
||||
ModelType::Local { .. } => unreachable!(),
|
||||
};
|
||||
|
||||
quote! {
|
||||
pub struct Create<'a> {
|
||||
crdt_client: &'a super::_prisma::PrismaCRDTClient,
|
||||
set_params: CreateParams,
|
||||
with_params: Vec<crate::prisma::#model_name_snake::WithParam>,
|
||||
}
|
||||
|
||||
impl<'a> Create<'a> {
|
||||
pub(super) fn new(
|
||||
crdt_client: &'a super::_prisma::PrismaCRDTClient,
|
||||
set_params: CreateParams,
|
||||
with_params: Vec<crate::prisma::#model_name_snake::WithParam>,
|
||||
) -> Self {
|
||||
Self {
|
||||
crdt_client,
|
||||
set_params,
|
||||
with_params,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with(mut self, param: impl Into<crate::prisma::#model_name_snake::WithParam>) -> Self {
|
||||
self.with_params.push(param.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn exec(
|
||||
self,
|
||||
) -> Result<crate::prisma::#model_name_snake::Data, crate::prisma::QueryError> {
|
||||
let res = #create_call;
|
||||
|
||||
#exec_body
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a model's `Actions::create` method
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```
|
||||
/// pub fn create(self, name: String, profile_id: i32, _params: Vec<SetParam>) -> Create<'a> {
|
||||
/// Create::new(
|
||||
/// self.client,
|
||||
/// CreateParams { .. },
|
||||
/// vec![]
|
||||
/// )
|
||||
/// }
|
||||
/// ```
|
||||
pub fn action_method(model: ModelRef) -> TokenStream {
|
||||
let args = create_params::args(model, Some(quote!(super)));
|
||||
|
||||
let create_params_constructor = create_params::constructor(model);
|
||||
|
||||
quote! {
|
||||
pub fn create(self, #(#args),*) -> Create<'a> {
|
||||
Create::new(
|
||||
self.client,
|
||||
#create_params_constructor,
|
||||
vec![]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,203 +0,0 @@
|
|||
use crate::generator::prelude::*;
|
||||
|
||||
use super::sync_id;
|
||||
|
||||
/// Generates definitions for a model's `CreateParams` and `CRDTCreateParams` structs
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```
|
||||
/// #[derive(Clone)]
|
||||
/// pub struct CreateParams {
|
||||
/// pub _params: Vec<SetParam>,
|
||||
/// pub name: String,
|
||||
/// pub profile_id: i32
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Clone, ::serde::Serialize, ::serde::Deserialize)]
|
||||
/// pub struct CRDTCreateParams {
|
||||
/// #[serde(default, skip_serializing_if = "Vec::is_empty", rename = "_")]
|
||||
/// pub _params: Vec<CRDTSetParam>,
|
||||
/// #[serde(flatten)]
|
||||
/// pub _sync_id: SyncID,
|
||||
/// pub name: String,
|
||||
/// pub profile_id: Vec<u8>
|
||||
/// }
|
||||
/// ```
|
||||
pub fn definition(model: ModelRef) -> TokenStream {
|
||||
let required_scalar_fields = model.required_scalar_fields();
|
||||
|
||||
let required_create_params = required_scalar_fields.iter().map(|field| {
|
||||
let field_name_snake = snake_ident(field.name());
|
||||
|
||||
let field_type = match field.field_type() {
|
||||
dml::FieldType::Scalar(_, _, _) => field.type_tokens(),
|
||||
dml::FieldType::Enum(e) => {
|
||||
let enum_name_pascal = pascal_ident(&e);
|
||||
|
||||
quote!(super::#enum_name_pascal)
|
||||
}
|
||||
_ => todo!(),
|
||||
};
|
||||
|
||||
quote!(#field_name_snake: #field_type)
|
||||
});
|
||||
|
||||
let mut scalar_sync_id_fields = model.scalar_sync_id_fields(&model.datamodel);
|
||||
|
||||
let required_crdt_create_params = required_scalar_fields
|
||||
.iter()
|
||||
.filter(|f| !scalar_sync_id_fields.any(|sf| sf.0.name() == f.name()))
|
||||
.map(|field| {
|
||||
let field_type = field.crdt_type_tokens(&model.datamodel);
|
||||
let field_name_snake = snake_ident(field.name());
|
||||
|
||||
quote!(#field_name_snake: #field_type)
|
||||
});
|
||||
|
||||
quote! {
|
||||
#[derive(Clone)]
|
||||
pub struct CreateParams {
|
||||
pub _params: Vec<SetParam>,
|
||||
#(pub #required_create_params),*
|
||||
}
|
||||
|
||||
#[derive(Clone, ::serde::Serialize, ::serde::Deserialize)]
|
||||
pub struct CRDTCreateParams {
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty", rename = "_")]
|
||||
pub _params: Vec<CRDTSetParam>,
|
||||
#[serde(flatten)]
|
||||
pub _sync_id: SyncID,
|
||||
#(pub #required_crdt_create_params),*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a list of a model's `CreateParams` as function arguments
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```
|
||||
/// name: String, profile_id: i32, _params: Vec<SetParam>
|
||||
/// ```
|
||||
pub fn args(model: ModelRef, namespace: Option<TokenStream>) -> Vec<TokenStream> {
|
||||
let mut required_args = model
|
||||
.required_scalar_fields()
|
||||
.into_iter()
|
||||
.map(|field| {
|
||||
let field_name_snake = snake_ident(field.name());
|
||||
|
||||
let typ = match &field.field_type() {
|
||||
dml::FieldType::Scalar(_, _, _) => field.type_tokens(),
|
||||
dml::FieldType::Enum(e) => {
|
||||
let enum_name_pascal = pascal_ident(e);
|
||||
|
||||
quote!(#(#namespace::)super::#enum_name_pascal)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
quote!(#field_name_snake: #typ)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
required_args.push(quote!(_params: Vec<SetParam>));
|
||||
|
||||
required_args
|
||||
}
|
||||
|
||||
/// Generates a constructor for the `CreateParams` struct
|
||||
/// that assumes all required fields have been declared beforehand.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```
|
||||
/// CreateParams {
|
||||
/// name,
|
||||
/// profile_id,
|
||||
/// _params
|
||||
/// }
|
||||
/// ```
|
||||
pub fn constructor(model: ModelRef) -> TokenStream {
|
||||
let required_args = model
|
||||
.required_scalar_fields()
|
||||
.into_iter()
|
||||
.map(|field| snake_ident(field.name()));
|
||||
|
||||
quote! {
|
||||
CreateParams {
|
||||
#(#required_args,)*
|
||||
_params
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a constructor for the CRDTCreateParams struct.
|
||||
/// Assumes all required fields are in scope.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```
|
||||
/// CRDTCreateParams {
|
||||
/// _param: {
|
||||
/// let mut params = vec![];
|
||||
///
|
||||
/// for _param in self.set_params._params {
|
||||
/// params.push(_param.into_crdt(&self.crdt_client).await);
|
||||
/// }
|
||||
///
|
||||
/// params
|
||||
/// },
|
||||
/// _sync_id: sync_id.clone(),
|
||||
/// name: self.set_params.name,
|
||||
/// profile_id: self
|
||||
/// .crdt_client
|
||||
/// .client
|
||||
/// .profile()
|
||||
/// .find_unique(crate::prisma::profile::local_id::equals(self.set_params.profile_id))
|
||||
/// .exec()
|
||||
/// .await
|
||||
/// .unwrap()
|
||||
/// .unwrap()
|
||||
/// .local_id
|
||||
/// }
|
||||
/// ```
|
||||
pub fn crdt_constructor(model: ModelRef) -> TokenStream {
|
||||
let crdt_create_params = model
|
||||
.fields()
|
||||
.into_iter()
|
||||
.filter(|f| {
|
||||
f.is_scalar_field()
|
||||
&& f.required_on_create()
|
||||
&& model
|
||||
.scalar_sync_id_fields(&model.datamodel)
|
||||
.all(|(sf, _)| sf.name() != f.name())
|
||||
})
|
||||
.map(|field| {
|
||||
let field_name_snake = snake_ident(field.name());
|
||||
|
||||
let value = sync_id::scalar_field_to_crdt(
|
||||
field,
|
||||
quote!(self.crdt_client.client),
|
||||
quote!(self.set_params.#field_name_snake),
|
||||
);
|
||||
|
||||
quote!(#field_name_snake: #value)
|
||||
});
|
||||
|
||||
quote! {
|
||||
CRDTCreateParams {
|
||||
_params: {
|
||||
let mut params = vec![];
|
||||
|
||||
for _param in self.set_params._params {
|
||||
params.push(_param.into_crdt(&self.crdt_client).await);
|
||||
}
|
||||
|
||||
params
|
||||
},
|
||||
_sync_id: sync_id.clone(),
|
||||
#(#crdt_create_params,)*
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
// use crate::generator::prelude::*;
|
||||
|
||||
// pub fn generate(model: &Model) -> TokenStream {
|
||||
// let model_name = snake_ident(&model.name);
|
||||
//
|
||||
// quote! {
|
||||
// pub struct Delete<'a> {
|
||||
// client: &'a super::_prisma::PrismaCRDTClient,
|
||||
// where_param: crate::prisma::#model_name::UniqueWhereParam,
|
||||
// with_params: Vec<crate::prisma::#model_name::WithParam>,
|
||||
// }
|
||||
//
|
||||
// impl<'a> Delete<'a> {
|
||||
// pub fn with(mut self, param: impl Into<crate::prisma::location::WithParam>) -> Self {
|
||||
// self.with_params.push(param.into());
|
||||
// self
|
||||
// }
|
||||
//
|
||||
// pub async fn exec(self) -> Result<Option<crate::prisma::#model_name::Data>, crate::prisma::QueryError> {
|
||||
//
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
|
@ -1,74 +0,0 @@
|
|||
pub mod actions;
|
||||
pub mod create;
|
||||
pub mod create_params;
|
||||
pub mod delete;
|
||||
pub mod owned;
|
||||
pub mod relation;
|
||||
pub mod set_param;
|
||||
pub mod shared;
|
||||
pub mod sync_id;
|
||||
pub mod update;
|
||||
|
||||
use crate::generator::prelude::*;
|
||||
|
||||
/// Things specific to the type of the model
|
||||
fn model_type_tokens(model: ModelRef) -> TokenStream {
|
||||
match &model.typ {
|
||||
ModelType::Relation { item, group } => {
|
||||
let item_field = model.field(item.at_index(0).unwrap()).unwrap();
|
||||
let item_def = relation::relation_key_definition(item_field, quote!(RelationItem));
|
||||
|
||||
let group_field = model.field(group.at_index(0).unwrap()).unwrap();
|
||||
let group_def = relation::relation_key_definition(group_field, quote!(RelationGroup));
|
||||
|
||||
quote! {
|
||||
#item_def
|
||||
|
||||
#group_def
|
||||
}
|
||||
}
|
||||
_ => quote!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate(model: ModelRef) -> TokenStream {
|
||||
let model_name_snake = snake_ident(&model.name);
|
||||
|
||||
if matches!(&model.typ, ModelType::Local { .. }) {
|
||||
return quote!(pub use crate::prisma::#model_name_snake;);
|
||||
}
|
||||
|
||||
let model_type_tokens = model_type_tokens(model);
|
||||
|
||||
let set_param_enums = set_param::definition(model);
|
||||
let sync_id_struct = sync_id::definition(model);
|
||||
let create_params = create_params::definition(model);
|
||||
|
||||
let create_struct = create::struct_definition(model);
|
||||
let update_struct = update::generate(&model);
|
||||
// let delete_struct = delete::generate(&model);
|
||||
|
||||
let actions_struct = actions::definition(model);
|
||||
|
||||
quote!(
|
||||
pub mod #model_name_snake {
|
||||
pub use crate::prisma::#model_name_snake::*;
|
||||
|
||||
#model_type_tokens
|
||||
|
||||
#set_param_enums
|
||||
|
||||
#sync_id_struct
|
||||
|
||||
#create_params
|
||||
|
||||
#create_struct
|
||||
|
||||
#update_struct
|
||||
|
||||
// #delete_struct
|
||||
|
||||
#actions_struct
|
||||
}
|
||||
)
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
use crate::generator::prelude::*;
|
||||
|
||||
use super::{create_params, sync_id};
|
||||
|
||||
/// Generates the body of an owned model's `Create::exec` function
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```
|
||||
/// let sync_id = SyncId { .. };
|
||||
///
|
||||
/// let params = CRDTCreateParams { .. };
|
||||
///
|
||||
/// let params_map = ::prisma_crdt::objectify(params);
|
||||
///
|
||||
/// self
|
||||
/// .crdt_client
|
||||
/// .create_operation(::prisma_crdt::CRDTOperationType::owned(
|
||||
/// #model_name_str,
|
||||
/// vec![::prisma_crdt::OwnedOperationData::Create(params_map)]
|
||||
/// ))
|
||||
/// .await;
|
||||
/// ```
|
||||
pub fn create_exec_body(model: ModelRef) -> TokenStream {
|
||||
let model_name_str = &model.name;
|
||||
|
||||
let sync_id_constructor = sync_id::constructor(model, quote!(self.set_params));
|
||||
|
||||
let crdt_params_constructor = create_params::crdt_constructor(model);
|
||||
|
||||
quote! {
|
||||
let sync_id = #sync_id_constructor;
|
||||
|
||||
let params = #crdt_params_constructor;
|
||||
|
||||
let params_map = ::prisma_crdt::objectify(params);
|
||||
|
||||
self
|
||||
.crdt_client
|
||||
._create_operation(::prisma_crdt::CRDTOperationType::owned(
|
||||
#model_name_str,
|
||||
vec![::prisma_crdt::OwnedOperationData::Create(params_map)]
|
||||
))
|
||||
.await;
|
||||
}
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
use crate::generator::prelude::*;
|
||||
|
||||
use super::sync_id::scalar_field_to_crdt;
|
||||
|
||||
/// Generates the struct definition for a relation's key
|
||||
pub fn relation_key_definition(field: FieldRef, struct_name: TokenStream) -> TokenStream {
|
||||
let fields = match &field.typ {
|
||||
FieldType::Relation { relation_info } => relation_info.fields.iter().map(|rel_field| {
|
||||
let field_name_snake = snake_ident(rel_field);
|
||||
let field = field
|
||||
.model
|
||||
.field(rel_field)
|
||||
.unwrap_or_else(|| panic!("Model {} has no field {}", field.model.name, rel_field));
|
||||
|
||||
let field_type = field.crdt_type_tokens(&field.datamodel);
|
||||
|
||||
quote!(#field_name_snake: #field_type)
|
||||
}),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
quote! {
|
||||
#[derive(Clone, ::serde::Serialize, ::serde::Deserialize)]
|
||||
pub struct #struct_name {
|
||||
#(pub #fields),*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a constructor for a relation's key
|
||||
pub fn relation_key_constructor(field: FieldRef, struct_name: TokenStream) -> TokenStream {
|
||||
let key_args = match &field.typ {
|
||||
FieldType::Relation { relation_info } => relation_info.fields.iter().map(|rel_field| {
|
||||
let field_name_snake = snake_ident(rel_field);
|
||||
let field = field.model.field(rel_field).unwrap();
|
||||
|
||||
let value = scalar_field_to_crdt(
|
||||
field,
|
||||
quote!(self.crdt_client.client),
|
||||
quote!(self.set_params.#field_name_snake),
|
||||
);
|
||||
|
||||
quote!(#field_name_snake: #value)
|
||||
}),
|
||||
_ => unreachable!(), // Item & group must be relations
|
||||
};
|
||||
|
||||
quote! {
|
||||
#struct_name {
|
||||
#(#key_args),*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates the body for a relation model's `Create::exec` function
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```
|
||||
/// let relation_item = RelationItem { .. };
|
||||
///
|
||||
/// let relation_group = RelationGroup { .. };
|
||||
///
|
||||
/// self
|
||||
/// .crdt_client
|
||||
/// ._create_operation(::prisma_crdt::CRDTOperationType::relation(
|
||||
/// #model_name_str,
|
||||
/// ::prisma_crdt::objectify(relation_item),
|
||||
/// ::prisma_crdt::objectify(relation_group),
|
||||
/// ::prisma_crdt::RelationOperationData::create()
|
||||
/// ))
|
||||
/// .await;
|
||||
/// ```
|
||||
pub fn create_exec_body(model: ModelRef) -> TokenStream {
|
||||
let model_name_str = &model.name;
|
||||
|
||||
let (relation_item_block, relation_group_block) = match &model.typ {
|
||||
ModelType::Relation { item, group } => {
|
||||
let relation_item_block = relation_key_constructor(
|
||||
model.field(item.at_index(0).unwrap()).unwrap(),
|
||||
quote!(RelationItem),
|
||||
);
|
||||
let relation_group_block = relation_key_constructor(
|
||||
model.field(group.at_index(0).unwrap()).unwrap(),
|
||||
quote!(RelationGroup),
|
||||
);
|
||||
|
||||
(relation_item_block, relation_group_block)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
quote! {
|
||||
let relation_item = #relation_item_block;
|
||||
|
||||
let relation_group = #relation_group_block;
|
||||
|
||||
self
|
||||
.crdt_client
|
||||
._create_operation(::prisma_crdt::CRDTOperationType::relation(
|
||||
#model_name_str,
|
||||
::prisma_crdt::objectify(relation_item),
|
||||
::prisma_crdt::objectify(relation_group),
|
||||
::prisma_crdt::RelationOperationData::create()
|
||||
))
|
||||
.await;
|
||||
}
|
||||
}
|
|
@ -1,181 +0,0 @@
|
|||
use crate::generator::prelude::*;
|
||||
|
||||
use super::sync_id::scalar_field_to_crdt;
|
||||
|
||||
struct SetParam {
|
||||
pub variant: TokenStream,
|
||||
pub into_match_arm: TokenStream,
|
||||
pub into_crdt_match_arm: TokenStream,
|
||||
pub from_pcr_set_impl: TokenStream,
|
||||
}
|
||||
|
||||
impl SetParam {
|
||||
pub fn new(field: FieldRef) -> Self {
|
||||
let model_name_snake = snake_ident(&field.model.name);
|
||||
let field_name_snake = snake_ident(field.name());
|
||||
let field_name_pascal = pascal_ident(field.name());
|
||||
|
||||
let variant_name = format_ident!("Set{}", field_name_pascal);
|
||||
|
||||
let variant = {
|
||||
let variant_type = field.type_tokens();
|
||||
quote!(#variant_name(#variant_type))
|
||||
};
|
||||
|
||||
let into_match_arm = quote!(Self::#variant_name(v) => crate::prisma::#model_name_snake::#field_name_snake::set(v));
|
||||
|
||||
let into_crdt_match_arm = {
|
||||
let to_crdt_block = scalar_field_to_crdt(field, quote!(client), quote!(v));
|
||||
|
||||
quote!(Self::#variant_name(v) => CRDTSetParam::#variant_name(#to_crdt_block))
|
||||
};
|
||||
|
||||
let from_pcr_set_impl = quote! {
|
||||
impl From<crate::prisma::#model_name_snake::#field_name_snake::Set> for SetParam {
|
||||
fn from(v: crate::prisma::#model_name_snake::#field_name_snake::Set) -> Self {
|
||||
Self::#variant_name(v.0)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
SetParam {
|
||||
variant,
|
||||
into_match_arm,
|
||||
into_crdt_match_arm,
|
||||
from_pcr_set_impl,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CRDTSetParam {
|
||||
pub variant: TokenStream,
|
||||
pub into_match_arm: TokenStream,
|
||||
}
|
||||
|
||||
impl CRDTSetParam {
|
||||
pub fn new(field: FieldRef) -> Self {
|
||||
let model_name_snake = snake_ident(&field.model.name);
|
||||
let field_name_snake = snake_ident(field.name());
|
||||
let field_name_pascal = pascal_ident(field.name());
|
||||
|
||||
let variant_name = format_ident!("Set{}", field_name_pascal);
|
||||
|
||||
let variant = {
|
||||
let variant_type = field.crdt_type_tokens(&field.datamodel);
|
||||
let field_name = field.name();
|
||||
|
||||
quote! {
|
||||
#[serde(rename = #field_name)]
|
||||
#variant_name(#variant_type)
|
||||
}
|
||||
};
|
||||
|
||||
let into_match_arm = {
|
||||
let relation_field_info = match &field.typ {
|
||||
FieldType::Scalar {
|
||||
relation_field_info,
|
||||
} => relation_field_info,
|
||||
_ => unreachable!("Cannot create CRDTSetParam from relation field!"),
|
||||
};
|
||||
|
||||
let ret = match relation_field_info.as_ref() {
|
||||
Some(relation_field_info)
|
||||
if
|
||||
field.model.name != relation_field_info.referenced_model // This probably isn't good enough
|
||||
=>
|
||||
{
|
||||
let relation_name_snake = snake_ident(relation_field_info.relation);
|
||||
let relation_model = field.datamodel.model(relation_field_info
|
||||
.referenced_model).unwrap();
|
||||
let relation_model_name_snake = snake_ident(&relation_model.name);
|
||||
|
||||
let referenced_sync_id_field = relation_model
|
||||
.sync_id_for_pk(relation_field_info.referenced_field)
|
||||
.expect("referenced_sync_id_field should be present");
|
||||
let referenced_sync_id_field_name_snake = snake_ident(referenced_sync_id_field.name());
|
||||
|
||||
let ret = quote!(crate::prisma::#model_name_snake::#relation_name_snake::link(
|
||||
crate::prisma::#relation_model_name_snake::#referenced_sync_id_field_name_snake::equals(v)
|
||||
));
|
||||
|
||||
match field.arity() {
|
||||
dml::FieldArity::Optional => {
|
||||
quote!(v.map(|v| #ret).unwrap_or(crate::prisma::#model_name_snake::#relation_name_snake::unlink()))
|
||||
}
|
||||
_ => ret,
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
quote!(crate::prisma::#model_name_snake::#field_name_snake::set(v))
|
||||
}
|
||||
};
|
||||
quote!(Self::#variant_name(v) => #ret)
|
||||
};
|
||||
|
||||
Self {
|
||||
variant,
|
||||
into_match_arm,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn definition(model: ModelRef) -> TokenStream {
|
||||
let model_name_snake = snake_ident(&model.name);
|
||||
|
||||
let set_param_fields_iter = model.fields().into_iter().filter(|f| {
|
||||
model
|
||||
.scalar_sync_id_fields(&model.datamodel)
|
||||
.any(|(id, _)| id.name() == f.name())
|
||||
|| f.is_scalar_field() && !(model.is_pk(f.name()) || model.is_sync_id(f.name()))
|
||||
});
|
||||
|
||||
let set_params = set_param_fields_iter.clone().map(SetParam::new);
|
||||
|
||||
let set_param_variants = set_params.clone().map(|p| p.variant);
|
||||
let set_param_into_match_arms = set_params.clone().map(|p| p.into_match_arm);
|
||||
let set_param_into_crdt_match_arms = set_params.clone().map(|p| p.into_crdt_match_arm);
|
||||
let set_param_from_pcr_set_impls = set_params.clone().map(|p| p.from_pcr_set_impl);
|
||||
|
||||
let crdt_set_params = set_param_fields_iter.map(CRDTSetParam::new);
|
||||
|
||||
let crdt_set_param_variants = crdt_set_params.clone().map(|p| p.variant);
|
||||
let crdt_set_param_into_match_arms = crdt_set_params.clone().map(|p| p.into_match_arm);
|
||||
|
||||
quote! {
|
||||
#[derive(Clone)]
|
||||
pub enum SetParam {
|
||||
#(#set_param_variants),*
|
||||
}
|
||||
|
||||
impl SetParam {
|
||||
pub async fn into_crdt(self, client: &super::_prisma::PrismaCRDTClient) -> CRDTSetParam {
|
||||
match self {
|
||||
#(#set_param_into_crdt_match_arms),*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#(#set_param_from_pcr_set_impls)*
|
||||
|
||||
impl Into<crate::prisma::#model_name_snake::SetParam> for SetParam {
|
||||
fn into(self) -> crate::prisma::#model_name_snake::SetParam {
|
||||
match self {
|
||||
#(#set_param_into_match_arms),*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub enum CRDTSetParam {
|
||||
#(#crdt_set_param_variants),*
|
||||
}
|
||||
|
||||
impl Into<crate::prisma::#model_name_snake::SetParam> for CRDTSetParam {
|
||||
fn into(self) -> crate::prisma::#model_name_snake::SetParam {
|
||||
match self {
|
||||
#(#crdt_set_param_into_match_arms),*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,124 +0,0 @@
|
|||
use crate::generator::prelude::*;
|
||||
|
||||
use super::{create_params, sync_id};
|
||||
|
||||
/// Generates the body of a shared relation's `Create::exec` function
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ### Atomic
|
||||
///
|
||||
/// ```
|
||||
/// let sync_id = SyncId { .. };
|
||||
///
|
||||
/// self
|
||||
/// .crdt_client
|
||||
/// ._create_operation(::prisma_crdt::CRDTOperationType::shared(
|
||||
/// #model_name_str,
|
||||
/// ::serde_json::to_value(&sync_id).unwrap(),
|
||||
/// ::prisma_crdt::SharedOperationData::create_atomic()
|
||||
/// ))
|
||||
/// .await;
|
||||
///
|
||||
/// for param in self.set_params._params {
|
||||
/// let crdt_param = param.into_crdt(self.crdt_client).await;
|
||||
///
|
||||
/// let param_map = ::prisma_crdt::objectify(crdt_param);
|
||||
///
|
||||
/// for (key, value) in param_map {
|
||||
/// self
|
||||
/// .crdt_client
|
||||
/// ._create_operation(::prisma_crdt::CRDTOperation::shared(
|
||||
/// #model_name_str,
|
||||
/// ::serde_json::to_value(&sync_id).unwrap(),
|
||||
/// ::prisma_crdt::SharedOperationData::update(key, value)
|
||||
/// ))
|
||||
/// .await;
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Unique
|
||||
///
|
||||
/// ```
|
||||
/// let sync_id = SyncId { .. };
|
||||
///
|
||||
/// let params = CreateCRDTParams { .. };
|
||||
///
|
||||
/// let params_map = ::prisma_crdt::objectify(params);
|
||||
///
|
||||
/// self
|
||||
/// .crdt_client
|
||||
/// ._create_operation(::prisma_crdt::CRDTOperationType::shared(
|
||||
/// #model_name_str,
|
||||
/// ::serde_json::to_value(&sync_id).unwrap(),
|
||||
/// ::prisma_crdt::SharedOperationData::create_unique(params)
|
||||
/// ))
|
||||
/// .await;
|
||||
/// ```
|
||||
pub fn create_exec_body(model: ModelRef) -> TokenStream {
|
||||
let model_name_str = &model.name;
|
||||
|
||||
let sync_id_constructor = sync_id::constructor(model, quote!(self.set_params));
|
||||
|
||||
let create_mode = match &model.typ {
|
||||
ModelType::Shared { create, .. } => create,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let the_meat = match create_mode {
|
||||
SharedCreateType::Atomic => {
|
||||
quote! {
|
||||
self
|
||||
.crdt_client
|
||||
._create_operation(::prisma_crdt::CRDTOperationType::shared(
|
||||
#model_name_str,
|
||||
::serde_json::to_value(&sync_id).unwrap(),
|
||||
::prisma_crdt::SharedOperationData::create_atomic()
|
||||
))
|
||||
.await;
|
||||
|
||||
for param in self.set_params._params {
|
||||
let crdt_param = param.into_crdt(self.crdt_client).await;
|
||||
|
||||
let param_map = ::prisma_crdt::objectify(crdt_param);
|
||||
|
||||
for (key, value) in param_map {
|
||||
self
|
||||
.crdt_client
|
||||
._create_operation(::prisma_crdt::CRDTOperationType::shared(
|
||||
#model_name_str,
|
||||
::serde_json::to_value(&sync_id).unwrap(),
|
||||
::prisma_crdt::SharedOperationData::update(key, value)
|
||||
))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
SharedCreateType::Unique => {
|
||||
let crdt_params_constructor = create_params::crdt_constructor(model);
|
||||
|
||||
quote! {
|
||||
let params = #crdt_params_constructor;
|
||||
|
||||
let params_map = ::prisma_crdt::objectify(params);
|
||||
|
||||
self
|
||||
.crdt_client
|
||||
._create_operation(::prisma_crdt::CRDTOperationType::shared(
|
||||
#model_name_str,
|
||||
::serde_json::to_value(&sync_id).unwrap(),
|
||||
::prisma_crdt::SharedOperationData::create_unique(params_map)
|
||||
))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
quote! {
|
||||
let sync_id = #sync_id_constructor;
|
||||
|
||||
#the_meat
|
||||
}
|
||||
}
|
|
@ -1,202 +0,0 @@
|
|||
use crate::generator::prelude::*;
|
||||
|
||||
/// Constructs the Sync ID for the given model.
|
||||
///
|
||||
/// ## Scalar Example
|
||||
/// ```
|
||||
/// @shared(id: unique_id)
|
||||
/// model User {
|
||||
/// pk Int @id
|
||||
/// id Bytes @unique
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// SyncID {
|
||||
/// id: #data_var.id
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## Relation Example
|
||||
///
|
||||
/// ```
|
||||
/// @shared(id: [location, pk])
|
||||
/// model File {
|
||||
/// pk Int @id
|
||||
///
|
||||
/// location_id Int
|
||||
/// location Location
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// SyncID {
|
||||
/// location_id: self
|
||||
/// .client
|
||||
/// .client
|
||||
/// .file()
|
||||
/// .find_unique(crate::prisma::location::local_id::equals(#data_var.location_id.clone()))
|
||||
/// .exec()
|
||||
/// .await?
|
||||
/// .id,
|
||||
/// pk: #data_var.pk.clone()
|
||||
/// }
|
||||
/// ```
|
||||
pub fn constructor(model: ModelRef, data_var: TokenStream) -> TokenStream {
|
||||
let model_name_snake = snake_ident(&model.name);
|
||||
|
||||
let args = model
|
||||
.fields()
|
||||
.into_iter()
|
||||
.filter(|f| model.is_sync_id(f.name()))
|
||||
.flat_map(|f| match &f.typ {
|
||||
FieldType::Scalar { .. } => vec![f],
|
||||
FieldType::Relation { relation_info } => relation_info
|
||||
.fields
|
||||
.iter()
|
||||
.map(|f| {
|
||||
model
|
||||
.field(f)
|
||||
.unwrap_or_else(|| panic!("{} has no field {}", model.name, f))
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
.map(|f| {
|
||||
let field_name_snake = snake_ident(f.name());
|
||||
|
||||
let val = scalar_field_to_crdt(
|
||||
f,
|
||||
quote!(self.crdt_client.client),
|
||||
quote!(#data_var.#field_name_snake),
|
||||
);
|
||||
|
||||
quote!(#field_name_snake: #val)
|
||||
});
|
||||
|
||||
quote! {
|
||||
super::#model_name_snake::SyncID {
|
||||
#(#args,)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates tokens to get the CRDT value of a scalar field.
|
||||
///
|
||||
/// ## Reguar Field
|
||||
/// For a field that has no connection to any model's sync ID,
|
||||
/// the value used will be `set_param_value`.
|
||||
///
|
||||
/// ```
|
||||
/// field String
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// #set_param_value
|
||||
/// ```
|
||||
///
|
||||
/// ## Sync ID Field
|
||||
/// For a field that is a foreign key, a query fetching the foreign model's
|
||||
/// corresponding Sync ID will be generated.
|
||||
///
|
||||
/// ```
|
||||
/// relation_id String
|
||||
/// relation RelationModel @relation(fields: [relation_id], references: [foreign_pk])
|
||||
/// ````
|
||||
///
|
||||
/// ```
|
||||
/// #client
|
||||
/// .relation()
|
||||
/// .find_unique(crate::prisma::relation::id::equals(#set_param_value))
|
||||
/// .exec()
|
||||
/// .await
|
||||
/// .unwrap()
|
||||
/// .unwrap()
|
||||
/// .foregin_sync_id
|
||||
/// ```
|
||||
|
||||
pub fn scalar_field_to_crdt(
|
||||
field: FieldRef,
|
||||
client: TokenStream,
|
||||
set_param_value: TokenStream,
|
||||
) -> TokenStream {
|
||||
match &field.typ {
|
||||
FieldType::Scalar {
|
||||
relation_field_info,
|
||||
} => relation_field_info
|
||||
.as_ref()
|
||||
.and_then(|relation_field_info| {
|
||||
let referenced_field_snake = snake_ident(relation_field_info.referenced_field);
|
||||
|
||||
let relation_model = field
|
||||
.datamodel
|
||||
.model(relation_field_info.referenced_model)
|
||||
.unwrap();
|
||||
let relation_model_snake = snake_ident(&relation_model.name);
|
||||
|
||||
let referenced_sync_id_field = relation_model
|
||||
.sync_id_for_pk(relation_field_info.referenced_field)
|
||||
.expect("referenced_sync_id_field should be present");
|
||||
|
||||
// If referenced field is a sync ID, it does not need to be converted
|
||||
(!field.model.is_sync_id(relation_field_info.referenced_field)).then(|| {
|
||||
let referenced_sync_id_field_name_snake =
|
||||
snake_ident(referenced_sync_id_field.name());
|
||||
|
||||
let query = quote! {
|
||||
#client
|
||||
.#relation_model_snake()
|
||||
.find_unique(
|
||||
crate::prisma::#relation_model_snake::#referenced_field_snake::equals(#set_param_value)
|
||||
)
|
||||
.exec()
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.#referenced_sync_id_field_name_snake
|
||||
};
|
||||
|
||||
match field.arity() {
|
||||
dml::FieldArity::Optional => {
|
||||
quote! {
|
||||
// can't map with async :sob:
|
||||
match #set_param_value {
|
||||
Some(#set_param_value) => Some(#query),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => query,
|
||||
}
|
||||
})
|
||||
})
|
||||
.unwrap_or(quote!(#set_param_value)),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a definition of a model's `SyncID` struct
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```
|
||||
/// #[derive(Clone, ::serde::Serialize, ::serde::Deserialize)]
|
||||
/// pub struct SyncID {
|
||||
/// pub id: i32,
|
||||
/// pub location_id: Vec<u8>
|
||||
/// }
|
||||
/// ```
|
||||
pub fn definition(model: ModelRef) -> TokenStream {
|
||||
let sync_id_fields = model.scalar_sync_id_fields(&model.datamodel).map(|field| {
|
||||
let field_type = field.1.crdt_type_tokens(&model.datamodel);
|
||||
let field_name_snake = snake_ident(field.0.name());
|
||||
|
||||
quote!(#field_name_snake: #field_type)
|
||||
});
|
||||
|
||||
quote! {
|
||||
#[derive(Clone, ::serde::Serialize, ::serde::Deserialize)]
|
||||
pub struct SyncID {
|
||||
#(pub #sync_id_fields),*
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
use crate::generator::prelude::*;
|
||||
|
||||
pub fn generate(model: &Model) -> TokenStream {
|
||||
let model_name_snake = snake_ident(&model.name);
|
||||
|
||||
quote! {
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
struct CRDTUpdateParams {
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty", rename = "_")]
|
||||
pub _params: Vec<CRDTSetParam>,
|
||||
#[serde(flatten)]
|
||||
pub _sync_id: SyncID,
|
||||
}
|
||||
|
||||
pub struct Update<'a> {
|
||||
client: &'a super::_prisma::PrismaCRDTClient,
|
||||
where_param: crate::prisma::#model_name_snake::UniqueWhereParam,
|
||||
set_params: Vec<SetParam>,
|
||||
}
|
||||
|
||||
impl <'a> Update<'a> {
|
||||
pub async fn exec(self) -> Result<Option<crate::prisma::#model_name_snake::Data>, crate::prisma::QueryError> {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,9 +21,9 @@ enum ModelSyncType<'a> {
|
|||
Local {
|
||||
id: FieldVec<'a>,
|
||||
},
|
||||
Owned {
|
||||
id: FieldVec<'a>,
|
||||
},
|
||||
// Owned {
|
||||
// id: FieldVec<'a>,
|
||||
// },
|
||||
Shared {
|
||||
id: FieldVec<'a>,
|
||||
},
|
||||
|
@ -56,7 +56,7 @@ impl<'a> ModelSyncType<'a> {
|
|||
|
||||
Some(match attr.name {
|
||||
"local" => Self::Local { id },
|
||||
"owned" => Self::Owned { id },
|
||||
// "owned" => Self::Owned { id },
|
||||
"shared" => Self::Shared { id },
|
||||
_ => return None,
|
||||
})
|
||||
|
@ -64,7 +64,7 @@ impl<'a> ModelSyncType<'a> {
|
|||
|
||||
fn sync_id(&self) -> Vec<FieldWalker> {
|
||||
match self {
|
||||
Self::Owned { id } => id.clone(),
|
||||
// Self::Owned { id } => id.clone(),
|
||||
Self::Local { id } => id.clone(),
|
||||
Self::Shared { id } => id.clone(),
|
||||
_ => vec![],
|
||||
|
@ -76,7 +76,7 @@ impl ToTokens for ModelSyncType<'_> {
|
|||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let variant = match self {
|
||||
Self::Local { .. } => "Local",
|
||||
Self::Owned { .. } => "Owned",
|
||||
// Self::Owned { .. } => "Owned",
|
||||
Self::Shared { .. } => "Shared",
|
||||
Self::Relation { .. } => "Relation",
|
||||
};
|
||||
|
@ -246,7 +246,7 @@ impl PrismaGenerator for SDSyncGenerator {
|
|||
|
||||
sync_type.and_then(|a| {
|
||||
let data_type = match a {
|
||||
ModelSyncType::Owned { .. } => quote!(OwnedOperationData),
|
||||
// ModelSyncType::Owned { .. } => quote!(OwnedOperationData),
|
||||
ModelSyncType::Shared { .. } => quote!(SharedOperationData),
|
||||
ModelSyncType::Relation { .. } => {
|
||||
quote!(RelationOperationData)
|
||||
|
@ -263,12 +263,12 @@ impl PrismaGenerator for SDSyncGenerator {
|
|||
let cond = quote!(if op.model == prisma::#model_name_snake::NAME);
|
||||
|
||||
let match_case = match a {
|
||||
ModelSyncType::Owned { .. } => {
|
||||
quote! {
|
||||
#op_type_enum::Owned(op) #cond =>
|
||||
Self::#model_name_pascal(serde_json::from_value(op.record_id).ok()?, op.data)
|
||||
}
|
||||
}
|
||||
// ModelSyncType::Owned { .. } => {
|
||||
// quote! {
|
||||
// #op_type_enum::Owned(op) #cond =>
|
||||
// Self::#model_name_pascal(serde_json::from_value(op.record_id).ok()?, op.data)
|
||||
// }
|
||||
// }
|
||||
ModelSyncType::Shared { .. } => {
|
||||
quote! {
|
||||
#op_type_enum::Shared(op) #cond =>
|
||||
|
|
|
@ -1,96 +0,0 @@
|
|||
use crate::{attribute::Attribute, prelude::*};
|
||||
|
||||
pub enum ModelType {
|
||||
Owned,
|
||||
SharedUnique,
|
||||
SharedAtomic,
|
||||
Relation,
|
||||
}
|
||||
|
||||
pub fn model_sync_type(model: &dml::Model, dml: &dml::Datamodel) -> ModelType {
|
||||
let type_attribute = model
|
||||
.documentation
|
||||
.as_ref()
|
||||
.map(Attribute::parse)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
match type_attribute.name {
|
||||
"owned" => ModelType::Owned,
|
||||
"shared" => ModelType::SharedUnique, // TODO: fix
|
||||
"relation" => ModelType::Relation,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
pub fn module(model: &dml::Model, dml: &dml::Datamodel) -> TokenStream {
|
||||
let model_name_snake = snake_ident(&model.name);
|
||||
|
||||
let set_params_enum = set_params_enum(model, dml);
|
||||
|
||||
let actions_struct = actions_struct(model, dml);
|
||||
|
||||
quote! {
|
||||
pub mod #model_name_snake {
|
||||
#set_params_enum
|
||||
|
||||
#actions_struct
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_params_enum(model: &dml::Model, dml: &dml::Datamodel) -> TokenStream {
|
||||
quote! {
|
||||
pub enum SetParam {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_fn(model: &dml::Model, dml: &dml::Datamodel) -> TokenStream {
|
||||
let required_scalar_fields = model.required_scalar_fields();
|
||||
|
||||
let args = required_scalar_fields.iter().map(|field| {
|
||||
let name = snake_ident(field.name());
|
||||
let typ = field.type_tokens(quote!(crate::prisma::));
|
||||
|
||||
quote!(#name: #typ)
|
||||
});
|
||||
|
||||
match model_sync_type(model, dml) {
|
||||
ModelType::Owned => {
|
||||
quote! {
|
||||
pub fn create(&self, #(#args),*, _params: Vec<SetParam>) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
ModelType::SharedUnique => {
|
||||
quote! {
|
||||
pub fn create(&self, #(#args),*, _params: Vec<SetParam>) {}
|
||||
}
|
||||
}
|
||||
ModelType::SharedAtomic => {
|
||||
quote! {
|
||||
pub fn create(&self, _params: Vec<SetParam>) {}
|
||||
}
|
||||
}
|
||||
ModelType::Relation => {
|
||||
quote! {
|
||||
pub fn create(&self, _params: Vec<SetParam>) {}
|
||||
}
|
||||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn actions_struct(model: &dml::Model, dml: &dml::Datamodel) -> TokenStream {
|
||||
let create_fn = create_fn(model, dml);
|
||||
|
||||
quote! {
|
||||
pub struct Actions<'a> {
|
||||
pub(super) client: &'a super::#CRDT_CLIENT
|
||||
}
|
||||
|
||||
impl<'a> Actions<'a> {
|
||||
#create_fn
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
use std::{collections::BTreeMap, fmt::Debug};
|
||||
use std::fmt::Debug;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
|
@ -22,18 +22,12 @@ pub struct RelationOperation {
|
|||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Type)]
|
||||
pub enum SharedOperationCreateData {
|
||||
#[serde(rename = "u")]
|
||||
Unique(Map<String, Value>),
|
||||
#[serde(rename = "a")]
|
||||
Atomic,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Type)]
|
||||
#[serde(untagged)]
|
||||
pub enum SharedOperationData {
|
||||
Create(SharedOperationCreateData),
|
||||
#[serde(rename = "c")]
|
||||
Create(Map<String, Value>),
|
||||
#[serde(rename = "u")]
|
||||
Update { field: String, value: Value },
|
||||
#[serde(rename = "d")]
|
||||
Delete,
|
||||
}
|
||||
|
||||
|
@ -44,35 +38,35 @@ pub struct SharedOperation {
|
|||
pub data: SharedOperationData,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Type)]
|
||||
pub enum OwnedOperationData {
|
||||
Create(BTreeMap<String, Value>),
|
||||
CreateMany {
|
||||
values: Vec<(Value, BTreeMap<String, Value>)>,
|
||||
skip_duplicates: bool,
|
||||
},
|
||||
Update(BTreeMap<String, Value>),
|
||||
Delete,
|
||||
}
|
||||
// #[derive(Serialize, Deserialize, Clone, Debug, Type)]
|
||||
// pub enum OwnedOperationData {
|
||||
// Create(BTreeMap<String, Value>),
|
||||
// CreateMany {
|
||||
// values: Vec<(Value, BTreeMap<String, Value>)>,
|
||||
// skip_duplicates: bool,
|
||||
// },
|
||||
// Update(BTreeMap<String, Value>),
|
||||
// Delete,
|
||||
// }
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Type)]
|
||||
pub struct OwnedOperationItem {
|
||||
pub id: Value,
|
||||
pub data: OwnedOperationData,
|
||||
}
|
||||
// #[derive(Serialize, Deserialize, Clone, Debug, Type)]
|
||||
// pub struct OwnedOperationItem {
|
||||
// pub id: Value,
|
||||
// pub data: OwnedOperationData,
|
||||
// }
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Type)]
|
||||
pub struct OwnedOperation {
|
||||
pub model: String,
|
||||
pub items: Vec<OwnedOperationItem>,
|
||||
}
|
||||
// #[derive(Serialize, Deserialize, Clone, Debug, Type)]
|
||||
// pub struct OwnedOperation {
|
||||
// pub model: String,
|
||||
// pub items: Vec<OwnedOperationItem>,
|
||||
// }
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Type)]
|
||||
#[serde(untagged)]
|
||||
pub enum CRDTOperationType {
|
||||
Shared(SharedOperation),
|
||||
Relation(RelationOperation),
|
||||
Owned(OwnedOperation),
|
||||
// Owned(OwnedOperation),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Type)]
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
ArchiveBox,
|
||||
ArrowsClockwise,
|
||||
Broadcast,
|
||||
CopySimple,
|
||||
Crosshair,
|
||||
|
@ -40,6 +41,10 @@ export default () => {
|
|||
<Icon component={ArchiveBox} />
|
||||
Imports
|
||||
</SidebarLink>
|
||||
{/* <SidebarLink to="sync"> */}
|
||||
{/* <Icon component={ArrowsClockwise} /> */}
|
||||
{/* Sync */}
|
||||
{/* </SidebarLink> */}
|
||||
</div>
|
||||
{library && <LibrarySection />}
|
||||
<Section name="Tools" actionArea={<SubtleButton />}>
|
||||
|
|
|
@ -8,15 +8,15 @@ const Row = tw.p`overflow-hidden text-ellipsis space-x-1`;
|
|||
const OperationItem = ({ op }: { op: CRDTOperation }) => {
|
||||
let contents = null;
|
||||
|
||||
if ('record_id' in op.typ) {
|
||||
if ('model' in op.typ) {
|
||||
let subContents = null;
|
||||
|
||||
if (op.typ.data === null) {
|
||||
if (op.typ.data === 'd') {
|
||||
subContents = 'Delete';
|
||||
} else if (op.typ.data === 'a' || 'u' in op.typ.data) {
|
||||
} else if ('c' in op.typ.data) {
|
||||
subContents = 'Create';
|
||||
} else {
|
||||
subContents = `Update - ${op.typ.data.field}`;
|
||||
subContents = `Update - ${op.typ.data.u.field}`;
|
||||
}
|
||||
|
||||
contents = (
|
||||
|
|
|
@ -77,7 +77,7 @@ export type BuildInfo = { version: string; commit: string }
|
|||
|
||||
export type CRDTOperation = { node: string; timestamp: number; id: string; typ: CRDTOperationType }
|
||||
|
||||
export type CRDTOperationType = SharedOperation | RelationOperation | OwnedOperation
|
||||
export type CRDTOperationType = SharedOperation | RelationOperation
|
||||
|
||||
/**
|
||||
* Meow
|
||||
|
@ -216,12 +216,6 @@ export type OperatingSystem = "Windows" | "Linux" | "MacOS" | "Ios" | "Android"
|
|||
|
||||
export type OptionalRange<T> = { from: T | null; to: T | null }
|
||||
|
||||
export type OwnedOperation = { model: string; items: OwnedOperationItem[] }
|
||||
|
||||
export type OwnedOperationData = { Create: { [key: string]: any } } | { CreateMany: { values: ([any, { [key: string]: any }])[]; skip_duplicates: boolean } } | { Update: { [key: string]: any } } | "Delete"
|
||||
|
||||
export type OwnedOperationItem = { id: any; data: OwnedOperationData }
|
||||
|
||||
/**
|
||||
* TODO: P2P event for the frontend
|
||||
*/
|
||||
|
@ -255,9 +249,7 @@ export type SetNoteArgs = { id: number; note: string | null }
|
|||
|
||||
export type SharedOperation = { record_id: any; model: string; data: SharedOperationData }
|
||||
|
||||
export type SharedOperationCreateData = { u: { [key: string]: any } } | "a"
|
||||
|
||||
export type SharedOperationData = SharedOperationCreateData | { field: string; value: any } | null
|
||||
export type SharedOperationData = { c: { [key: string]: any } } | { u: { field: string; value: any } } | "d"
|
||||
|
||||
export type SortOrder = "Asc" | "Desc"
|
||||
|
||||
|
|
Loading…
Reference in a new issue