#Core Haxe
1 messages ยท Page 6 of 1
they are private, its just you are in the class itself i suppose, that said, cant hurt to make them @:noCompletion
i'm not ๐ค
Its weird that i'm getting a _lastOrder as well because that's meant to be a getter and a non entity function
public var lastOrder(get, never):Order;
function get_lastOrder() {
return this.orders[orders.length - 1];
}
no idea why that's there
maybe a nightly quirk?
will have to check, i know it does some things with getters / setters
hard to tell
yeah, its weird i don't think i saw this in the client side of the application
this is the server side
and actually, going back to my old question about the timestamp setter
if you're making them all have getters and setters anyway, i wonder if i can just override it ๐ค
oh... probably not. It's gonna be in the same class lol
so actually, they are set public, feels like a bug / oversight
should be better now
class EntityTools {
public static function insertOrUpdate<T:IEntity>(entity:T, table:String, ?succ:T->Void, ?err:Dynamic->Void) {
trace("test");
var e:Dynamic = cast entity;
e.add().then(successItem.bind(succ, true), (err) -> trace(err));
}
static function successItem<T>(?callback:T->Void, log:Bool, entity:T) {
if (callback != null) {
callback(entity);
}
trace('here');
}
}
Can confirm that this works ๐
public var lastOrder(get, never):Order;
function get_lastOrder() {
return this.orders[orders.length - 1];
}
This adds a column lastOrder into the table
not sure if intentional, the behaviour seems inconsistent
@:exposeId
@:fieldset(NoRelations, [!orders, !account])
class Position implements IEntity {
@:ignore public var client:Bool = #if client true #else false #end;
@:ignore public var rest:RestExchangeBase;
public var account:Account;
@:size(20) public var id:String;
public var symbol:String;
public var side:Side = None;
public var price:Float = 0;
public var quantity:Float = 0;
public var liq:Float;
public var open:Int = 0;
public var startTime:Float = 0;
public var endTime:Float = 0;
public var stopOrder:Order;
public var takeOrder:Order;
public var leverage:Float = 1.0;
@:cascade public var orders:Array<Order> = [];
public var updated:Float = 0;
}```
with this structure setting `stopOrder` and `takeOrder` to `null` doesn't remove the reference, in this particular case i'd expect the "reference" to be zero'd in some way
either by setting to actual 0 or something like -1
If i want to rename a column, how do I go about it without data loss? will e2 pick it up or should i rename in the db manually, rename the class locally and then everything is good?
looks like a bug
you mean, you do:
myPosition.stopOrder = null;
myPosition.update().then(...);
and stopOrder isnt null in the db?
you will defo lose data currently if you rename a field in the haxe classes, so you have to do it manually, eventually i want to have a migration system, but it doesnt exist yet, and im not 100% sure what it would look like
correct
defo feels like a bug, but one i thought i would have encountered by now... which seems odd
crazy, yeah:
function testBasic_NullRef(async:Async) {
var mainEntity = createEntity("this is a string value for entity #1", 123, 456.789, true, new Date(2000, 2, 3, 4, 5, 6));
mainEntity.entity1 = createEntity("sub entity", 456, 789.123, false, new Date(2001, 3, 4, 5, 6, 7));
profileStart("testBasic_Primitives");
measureStart("add()");
mainEntity.add().then(entity -> {
measureEnd("add()");
Assert.equals(2, entity.basicEntityId);
Assert.equals("this is a string value for entity #1", entity.stringValue);
Assert.equals(123, entity.intValue);
Assert.equals(456.789, entity.floatValue);
Assert.equals(true, entity.boolValue);
Assert.equals(new Date(2000, 2, 3, 4, 5, 6).toString(), entity.dateValue.toString());
Assert.equals(1, entity.entity1.basicEntityId);
Assert.equals("sub entity", entity.entity1.stringValue);
Assert.equals(456, entity.entity1.intValue);
Assert.equals(789.123, entity.entity1.floatValue);
Assert.equals(false, entity.entity1.boolValue);
Assert.equals(new Date(2001, 3, 4, 5, 6, 7).toString(), entity.entity1.dateValue.toString());
measureStart("refresh()");
return entity.refresh();
}).then(entity -> {
measureEnd("refresh()");
Assert.notNull(entity.entity1);
entity.entity1 = null;
return entity.update();
}).then(entity -> {
Assert.isNull(entity.entity1);
return entity.refresh();
}).then(entity -> {
Assert.isNull(entity.entity1);
profileEnd();
async.done();
}, error -> {
trace("ERROR", error);
});
}
the last Assert.isNull(entity.entity1); fails... ๐ฎ
I think it's because its an Int column and you can't null it by default so I think it makes sense in this case it should be something like 0 or -1
i think you can null an int column in the db
you have to set it to be nullable tho
that shouldnt be an issue, the schema is created by entities, im not even sure thats the case though... i think its just not nulling things, it might even be a db-core issue
so thats a bit crazy, so when it builds up the recordset to add into the db in the update it creates:
{
basicEntityId : 2,
stringValue : this is a string value for entity #1,
intValue : 123,
floatValue : 456.789,
boolValue : 1,
dateValue : 2000-03-03 04:05:06
}
notice how there is no entity1 field, which means the update sql simple wont include it
it means, in the "toRecord" code in entities2 there must be a != null
yeah, i defo relied on that behaviour a lot in direct use
toRecordFn.code += macro @:privateAccess {
var record = new db.Record();
$b{[for (entityField in entityDefinition.primitiveFields()) {
macro {
record.field($v{entityField.name}, entities.EntityManager.instance.convertPrimitiveToDB($i{entityField.name}, $v{entityField.type}, $v{entityField.options}));
}
}]}
$b{[for (entityField in entityDefinition.entityFields_OneToOne()) {
var foreignKey = entityField.foreignKey();
macro {
if ($i{entityField.name} != null) {
record.field($v{entityField.name}, $i{entityField.name}.$foreignKey);
}
}
}]}
return record;
}
yup
i need to think about this, seems intentional
yeah, there are some cases where you don't want to submit things that are null
exactly, like if you are just updating the, say, name of an entity, it could nuke your relationships
i mean, if you loaded it from the db, then you would be fine... or if you loaded it from network (serializers) and called .refresh, then you would be fine also
but if you forget to .refresh, then you could nuke your 1 -> 1's :/
would making a field be marked as nullable make this work?
i dont think so...
yeah, both "paths" are valid, thats the issue
i almost think you want a sort of "explicity nulled" or something, but i think entities internally might also do ".entity1 = null"
then another issue occurs there, how do you make a compatible syntax for that
for haxe there's 0 = null or 1 = object
you would need a second thing to flip, no?
null would be null in the db
stopOrder is an object, so it would be 0, it would be null
though i suspect the same thing happens for primitives also
actually, no, it doesnt
yeah, i've never had this occur outside of entities
$b{[for (entityField in entityDefinition.primitiveFields()) {
macro {
record.field($v{entityField.name}, entities.EntityManager.instance.convertPrimitiveToDB($i{entityField.name}, $v{entityField.type}, $v{entityField.options}));
}
}]}
im not sure what you mean here, its clearly an e2 issue, its not nulling the db entry that links stopOrder... and its because its skipping "things that are null", which is reasonable in one use case, and not in another (even using the same entity you can hit both use cases)
a response to though i suspect the same thing happens for primitives also but I also never used anything but primitives outside of e2
lol
yeah, primitives are fine, there isnt the "if != null"
for relationships there is, and its reasonable, but also not reasonable depending on use case... :/
var entity = new Entity();
entity.entityId = 1;
entity.name = "some name";
entity.update(); // <--- just nuked all relationships if i implement a fix (ie, remove the != null check)
it seems like a usecase that wouldnt happen, BUT, in my apps, entities often come down the wire to and from the client (serialized) and are partial
seems like it would be a common thing to do with api's as well, only send out what's changed
OK, i think i have a solution, just need to write a bunch of UTs to test it
public function field(name:String, value:Any = null):Any { // if value is non null, this is effectively a setter
if (value != null) {
data.set(name, value);
return value;
}
return data.get(name);
}
this shitty pattern in db-core just bit me too
well, it works:
just dunno if ive broken a bunch of other stuff in the process
this should be sorted now
nice! thanks
hopefully nothing is broken
likely there is tho
i vaguely remember trying to do something like this in my app side and ran into some complication
just can't recall what it was
i dont think there is tbh... were going to give other projects the once over, but the UTs show that none of the "normal" behaviour has changed since they all pass - and they are quite comprehensive
nice!
where would I go to start implementing the currency/json types
i'm guessing its going to be a simple mapping of types to dbcore
cause you did add it to core already
yeah, so, just so you know... the change was fairly simple: entities2 already creates setters for 1 -> 1 entities, this is because it doesnt like updated refs, and prefers to copy over the values if you do someEntity.subEntity = anotherEntity, in that scenario, the subEntity wont actually be a ref to anotherEntity but its data will be copied over (this is also so you dont nuke data, a nice feature in itself i forgot about)
this is extended now to "explicitlyNulled", so internally, e2 will bypass the setter, and prefer to directly set the variable (_subEntity), but in user code, if you explicitly set an entity to null, it will hold a flag for that that entity was nulled, and it will then write an actual null to the db
i dont think its going to be quite that straight forward tbh... it doesnt exist in db-core either
oh cool, why copy rather than use the existing object? Isn't that more work?
because you can nuke data
I'm sure you added decimal
Oh, i'm confused I defo remember asking you for the decimal type and i'm sure you added it
maybe you just parsed it on the incoming
decimal is stored as string in the db but i think you had it get parsed into float, so probably a db-mysql thing instead then
probably, yeah
i think a new type needs to be added to db-core, that maps to "that other one" i cant remember what it is now, and then e2 needs that mapping too
maybe Currency on both sides is "fine"
I think so
but it still needs plenty of mapping, i also suspect that the Currency abstract will come through in e2 as a Float (since the macro it uses followWithAbstracts) - so that will need to handled specially (i think)
Its clear
i tried doing it the other day with json but couldn't get it to work so likely more complicated than i thought
just have a getter that parses it locally instead
or i just missed a spot
but i tried to follow the Date type around
also learnt that you could have enum's named the exact same name as a haxe type from that lol
think i'm done with 99% of e2 porting
these kinds of things are a pita, but also a blessing. Found lots of things that could be done better along the way
e2 convert? 
I think so, now its time to figure out the workflows ๐
excuse the interjection - what's e2 ?
just a shorthand way of saying it x)
Basically, a class represents a database Table
ORM mapping then
correct
ok - thanx - just curious core haxe looks like it's headed to become Haxe Django
cool
I'm having issues with serialising IEntity's to send out over a websocket
properties not existing etc
are there any solutions
I'm assuming the issue is something like, none of the serializers know that var account:Account will turn into a property and may even have a different name, so on unserialising its referencing the wrong things?
all the data is there, its just not accessible ๐ค
what does your serialization look like?
I think I may have figured it out
I didn't realise how deep the serialisation was going so the things it was serialising wasn't really things i wanted to serialise
might be a coincidence that it only started occuring post e2 integration
what is the serialization lib?
std haxe
haxe. Serializer?
yeah
filtrek told me a cool feature of it and I think it actually does work fine, was just an error that could come from e2 related things
might wanna consider using this: https://github.com/core-haxe/serializers
you just do implements IHaxeSerializable on the entities and you will get to and from string functions
i use them alot with entities and they work fine
you will get to and from string functions
sorry, i meanfunction serialize():String;andfunction unserialize(data:Any):Void;functions
can you ignore properties?
yeah, @:ignore iirc - same as entities
I'm guessing you can't serialise an enum if it's the 'root' has to be a class?
why would you serialize an enum? You would just serialize enum values, no?
maybe im not understanding
but you would only serialize uses of that, not the enum itself, like var myFoo:Foo = A("bob")
i think it would work, it uses haxe.Serializer under the hood, and i think that works with things like that
passing thought but is there a way to override that
I have something in an entity that I don't want to go to the db, so its marked as @:ignore but I would want it to be serialised
i suppose a "@:force" could be added, but it all feels very "not the way entities is supposed to be used", the whole point (really) is that your entity is a data unit, not a dumping ground for "random" shit
It certainly isn't random shit
well, buts its not "entity shit" either
it's a data unit, so it's the place to put it
but a data unit that shouldnt be serialized to the db, but should be serialized to and from the client? Doesnt sound right
lemme guess, that cache thing again on date tools?
class Position implements IEntity {
@:ignore public var client:Bool = #if client true #else false #end;
public var account:Account;
@:size(20) public var id:String;
public var symbol:String;
}
I have a client property that tells the client that the object is created on the client or server
if the object is sent from the server, it should take priority over the client created version
this flag has no real business being in the database
client can't update the db
this flag has no real business being in the database
then i would argue it has no place being in the entity, not sure what alt system you would use, but i think if you have this sort of "well, this is in the entity, but its not really in the entity" then its sort of a misuse of the entity itself (imo)
function createOrder(position:Position, source:ClientOrServer)... or something
and?
class OrderRequest implements IHaxeSerializable {
var position:Position;
var source:ClientOrServer;
var someOtherParam:Int
}
sending entites "as is" is fine i suppose, you will, imo, quickly get into a situation where you'll want additional (non-entity) data passed through also, so you'll end up shoe-horning it into the entity "because its easier"
eg, this is my UserApi interface... which is used to build an RPC interface (transport doesnt matter, http, rpc, etc)
i'm defo putting some "non entity" stuff in there, and not treating it as a data-only thing
i'm treating it more like a "HandlePositionTableData" zone
exactly, its a misuse... i mean, its "fine", but i think you'll either need to "deal" with it being in the db, or ill add a "force" to serializers that opens it up for abuse (though thats not really my problem)
i think eventually, you are going to end up wanting an RPC type interface anyway rather than just passing a single entity back and forth
another thing to learn... ?!
i really don't even know what the difference is between rpc and what i'm doing currently
well, its not that much more, i dont mean a PROPER rpc system, i just mean that instead of passing and entity back and forth you pass a request / response that wraps and entity (or 100)
the main issue i'm solving with the current setup is basically making the mysql db "event-like"
class OrderRequest implements IHaxeSerializable {
var position:Position;
var requetor:User;
var someOtherEntity:Entity;
var source:ClientOrServer;
var someOtherParam:Int
}
if you are using the "single entity" approach, then you would end up creating a new entity call "OrderRequest" and that is defo not correct (unless you wanted OrderRequest in the db ofc, which is perfectly reasonable in itself)
your projects sounds very tidy
๐
you would probably hate to see some of my entities x)
It just seems like the right place for it, but maybe that's just where my ignorance level is at
most are pretty much "just entities" though
just 1 or 2 aren't
i'll probably revisit this at some point when i get to that stage of the project
I've managed to some how create an infinite loop of entity creations
Any idea what could cause this? The way I triggered it was on the client side I pressed an action button repetitively a few times, the writes are all completed but now it seems to be stuck in a perpetual read cycle
another action just occurred and took it out of it the cycle
class Main {
static function main() {
trace("Hello, world!");
new Main();
}
var db:IDatabase;
public static var universe:Universe;
function new() {
db = DatabaseFactory.instance.createDatabase(DatabaseFactory.MYSQL, {
database: 'test',
host: 'localhost',
user: 'root',
pass: 'password'
});
EntityManager.instance.database = db;
connected();
var timer = new Timer(200);
timer.run = this.loop;
}
function connected() {
var user = new User();
user.name = "Test Main";
var pos = new Position();
pos.symbol = 'BTCUSDT';
pos.orders = [];
pos.add().then((position) -> {
for (i in 0...2) {
var order = new Order();
order.exchange = 'Bybit';
order.symbol = 'BTCUSDT';
order.side = 'Buy';
order.price = 10;
order.quantity = 0.5;
order.add().then((response) -> {
pos.orders.push(response);
pos.update().then(null, (err) -> trace(err));
}, (err) -> trace(err));
}
}, (err) -> trace(err));
}
}
Was trying to isolate the loop bug and came across another bug
In this example, i'd expect there to be 2 orders in the pos.orders array
but I'm getting 4 in the database (2x duplicates)
maybe just a side effect of the loop/async nature here, but thought i'd mention just incase
looking at my db I do think reference duplication does seem to occur in general
i've got 13 orders linked to a position in my main db and there's 144 references :o
I think what's happening there is every table.update() a new reference is being added to the database
function connected() {
var user = new User();
user.name = "Test Main";
var pos = new Position();
pos.symbol = 'BTCUSDT';
pos.orders = [];
pos.add().then((position) -> {
for (i in 0...2) {
var order = new Order();
order.exchange = 'Bybit';
order.symbol = 'BTCUSDT';
order.side = 'Buy';
order.price = 10;
order.quantity = 0.5;
order.add().then((response) -> {
pos.orders.push(response);
pos.update().then(null, (err) -> trace(err));
}, (err) -> trace(err));
}
}, (err) -> trace(err));
}
this seems a very weird way to use entities
I don't actually use it like that, that's just the repro
var pos = new Position();
pos.symbol = 'BTCUSDT';
pos.orders = [];
for (i in 0...2) {
var order = new Order();
order.exchange = 'Bybit';
order.symbol = 'BTCUSDT';
order.side = 'Buy';
order.price = 10;
order.quantity = 0.5;
pos.orders.push(order);
}
pos.add()...
are you still adding the order though...
if you do want to manually add orders, and later associate them with the position, i think there is meta for that... "@:mustExist" or something
in my app there is no for loop adding orders
that all said and done, even though its not a pattern i use myself, i would expect something like this to be fine, so if it doesnt work, its a bug:
position.add();
order1.add();
order2.add():
position.orders = [order1, order2]
position.update();
(obviously promise based in reality, code above is just psuedo)
in my app it works like this
each press of the green button is 1 more order added to the position
4 clicks of the button is 4 orders associated to the position
but here's what the reference table looks like
so yeah, you are adding new orders that you are add()'ing to an existing positions, normally i would just add them to the position.order array and call update... but, i still think your pattern is perfectly reasonable
i think what is happening is you are add()'ing the order manually, then associating it with the position, then updating, and that, internally, is add()'ing it again
if you annotate the order array with @:mustExist, does that change / help?
This is defo something that's occuring, does that mean i'm also duplicating in my orders table? hmm.. will check
will check the mustexist thing now
keep in mind that mustexist also changes the behaviour, it means "my" pattern of "just add the orders to the array and call update" wont work at all anymore... the entities "must exist" in the db already
hmmmmmmmmmmm, i think I coded things with that idea in mind but will confirm
same behaviour
maybe not the right flag?
it is the right flag
but yeah, an order shouldn't implicitly be added to the orders array on a position
an order can reject, not be filled yet etc etc
so the order is an entity in its own right, and then later, based on some status, you associate it with a position?
correct
so yeah, it makes perfect sense, and feels like a bug... as i mentioned, the pattern you are using feels perfectly valid, even if i would maybe approach it somewhat differently, so ill create some UTs and see if i can repro (not going to happen now though, too much on)
no worries
might be related to another thing, i was noticing that my entities were getting created a ton of times, so i suspect what's happening is
they are getting created in proportion to the number of references around
but the arrays themselves seem accurate
like if there's 10 additional records in the db, i don't think i'm actually getting 10 duplicates locally. Confirmed, no additional records locally, so maybe you're handling the duplicates?
Curious, you said you would handle it differently - how would you handle it?
If I have a trigger on a table will e2 remove that?
I can do the same thing in code, just wondering if I should forget about them as an option
i doubt it... but defo test that, dont take my word for it ๐
it seems like many-to-many relationships works, but how would I construct a find query on them?
like, find all items with tags x, y and z in entity MyTable
i think you'll have to use a raw query for now, with inner joins, its not ideal because it means "knowing" about the table structure, but glint has had to do it for an "advanced" find
think i can repro this
there's a good chance your existing projects just have phantom data in regards to this issue
i think thats exactly what i just found
cleaning up is easy, making sure it doesnt happen again is something ill need to repro in a UT
The repro i gave should be easy to UT it right?
i just tried this with a few thousand orders but I got tons of duplicates in the db, admittedly it is a system i'm only just working on now, but want to know how this behaviour is meant to work
I think I understand what's going on, when I add the entity that references an order that isn't added to the database, it will add that order to the database. But I think what's happening is, every time that parent entity gets updated, it readds the order to the database because maybe it didn't get marked as being added?
Haven't confirmed, mostly just spilling thoughts as i wait for db to delete 60k records x)
ive been working towards repro'ing it in a unit test (that was last week though), for simple cases (to my surprise) i didnt see the issue - but there certainly is one, i just need to hone in on what the sequence is
.update could be a good hint
ok, actually, i did update, albeit a simple case
class Object1 implements IEntity {
public var name:String;
public var array_1:Array<Object2> = [];
@:cascade public var array_2:Array<Object2> = [];
}
class Object2 implements IEntity {
public var name:String;
}
class Object1 implements IEntity {
public var name:String;
public var counter:Int = 0;
public var array:Array<Object2> = [];
}
class Object2 implements IEntity {
public var name:String;
}
var foo = new Object1();
var bar = new Object2();
bar.name = "Test";
foo.array.push(bar);
for (i in 0...10) {
foo.counter = i;
foo.update();
}
I think this might be a simplified version of what's going on in my app
it doesn't happen on all of them but some of them so it might be a case of increasing the loop counter. Maybe it's an async related issue?
you arent using promises properly
you are like "fire and forgetting" them
you are expecting them to be sync here and they arent (at all)... especially since your db is remote
but there is an issue anyway, i can see it in my dbs, so for sure, there is something going on
It was pseudo code for the app flow, i'm not actually using it like that
it might still repro the issue
because i'm not doing that in my app and still get the dupes
it's important to note this though, because, the thing that's having issues is Object2 is the one getting duplicated, not Object1
I'm interfacing with Object2 so maybe Object1 isn't handling the async properly
maybe object2 is returning the done callback before array has finished doing the insert ops (and toggling whatever flags it needs)
Yea, if I preinsert all the orders, there's 0 duplicates
if you are doing something like this though:
for (i in 0...10) {
foo.counter = i;
foo.update();
}
Then odd things are going to happen... you need to wait for the update to finish before updating again, because things could be in a weird state
im starting to consider that this is whats happening in my apps somehow, i defo have orphans, but im just not sure where they are coming from because in the unit tests, which is a somewhat controlled environment, it doesnt happen
do you have a somewhat minimal repro of when it doesnt work (and the preinsert for good measure), im not surprised (i dont think) that if you preinsert its all fine, because then you are just updating an association
i actually think i might need to look into a locking mechanism, but these can be messy and i wondering if then entities is trying to do "too much"
var i = 0;
var waiting = false;
while (i < 10) {
if (waiting) {
continue;
}
waiting = true;
foo.counter = i;
foo.update().then(function(_) { i++; waiting = false}, null);
}```
Defos, I was adding a system and locking until a response doesn't affect this particular issue, it results in the same
it will heavily add to latency issues as well, preadding the orders to the db (with no lock required here) causes the app to lock up a bit altho i don't actually understand why this occurs
Just an additional note, i'm not sure if the above will repro, its just the kind of flow going on here, maybe try with 100 loops
I haven't really nailed down when its occuring, but my suspicion is it occurs when you have to reupdate the same Object2
this will likely skip a bunch of items... its not really a lock, more of a "skip"...
yeah, the latency is what im worried about tbh....
is it only ever one call coming in that is adding the objects? Because if it is, then it blows my idea out the water - any actual code for me to see? (or is it top secret kinda stuff? )
@:exposeId
class Position implements IEntity {
public var symbol:String;
public var account:Account;
@:cascade public var orders:Array<Order> = [];
}
@:exposeId
class Order implements IEntity {
public var symbol:String;
public var price:Float;
public var account:Account;
}
orders[aid] = getAllOrdersFromBybitCryptoExchange();
for (order in orders[aid]) {
EcsTools.set(aid, new PositionOrder(order));
}```
We start in `AccountSync.hx` to get all the orders from exchange API then push them out to another system
Then we go to `PositionSystem.hx` which receives the above orders
```hx
override function update(_) {
iterate(orders, (entity) -> {
if (handleOrder(account, order)) {
universe.deleteEntity(entity);
}
});
for (p in _positions) {
//only attempt to update a position when we have a new version and not in the process of updating
if (!updatingPosition.exists(p.pid) || (!updatingPosition[p.pid] && p.updated > lastUpdated[p.pid])) {
trace('here');
updatingPosition[p.pid] = true;
lastUpdated[p.pid] = p.updated;
p.update().then(function(_) {
emitPositionUpdate(p);
updatingPosition[p.pid] = false;
}, (err) -> {
updatingPosition[p.pid] = false;
trace(err);
});
}
}
function handleOrder(account:AccountId, order:Order) {
if (!_positions.exists(pid)) {
var state = settingUpPosition.get(pid);
if (settingUpPosition.exists(pid) && state) {
return false;
}
trace('setting up position');
settingUpPosition.set(pid, true);
EPosition.find(query($account == account && $symbol == order.symbol)).then((position) -> {
//create a position if null and add it the db, then remove the lock otherwise add the position to a local map and continue on
});
return false;
}
switch (order.status) {
case FILLED | PARTIALLY_FILLED:
p.addOrder(order);
//triggers `p.updated` timestamp to get raised
default:
}
}
That's not a repro or anything, but that's an exact cut down version of what's going on in my app
iterate(orders, (entity) -> {
if (handleOrder(account, order)) {
universe.deleteEntity(entity);
}
});
``` receives the updates from `AccountSync.hx`'s EcsTools.set....
I couldn't seem to repro it, I did give it a few tries :/
not everything in my app is getting duplicated tho, there's like 1000 demo orders i'm syncing for the test and there will be a bunch that don't duplicate then all of a sudden one will get duplicated like 8 times
and it repeats like that sporadically
hopefully there's some hint or correlation to your project in there
yeah, the orphans in my db are defo not "all the time"... so it feels like a race condition of sorts, which is hard to repro locally since its "too fast"... i think ill plug the UTs into a remote db at some point and see if that works better (as in worse ๐ )
I did my tests on a remote db
the only thing i didn't try is doing a loop of 1000 for some reason lol
just tried, made no difference test is probably too simple
seperate question, on an entity if I do orders = []; would it delete all the relationships from the respective table?
this is exactly the path me and @old juniper are starting to go down... im not sure it explains everything, but it does explain some things... the question is how to fix
yeah, i just randomly walked into this issue just now
from what I expect, these relationships should be gone but they just appeared randomly
yeah, there is defo a bug there, but im not sure if its the "whole picture" or part of it
it's certainly curious how we've all ended up in a similar place though, must be something simple..... back to the repro ๐
repro attempt worked perfectly :/
well that's interesting ๐
I don't have a clue what's going on here my app isn't connected ๐คฃ
cursed as hell, i've restarted literally every part of the chain and these things won't delete
right click -> empty table did the job
Is it possible to limit how deep the cascade goes?
I only want to remove the relationships but i suspect its removing the og data as well
maybe not.... something else weird is going on
I'm inserting 68 records but db always seems to have an arbitrary amount less than 68
exchange.data.Order.count(query($account == 2)).then((count) -> {
trace('$count order records');
trace('${orders.length} orders');
}, (err) -> trace(err));```
but i guess cascade does remove the original order records, which isn't really what I want ๐ค
it's odd tho, because if that was what was going on I should end up with 0 orders rather than > 0
(don't mind me, just rubber ducking a lil bit now lol)
class Foo extends IEntity {
public var data:Int = 0;
}```
1 benefit of this applying a default value is that all prior entries that existed prior to the point of column get auto filled in
@toxic stratus - at some point "soon", i have some pretty huge updates for e2... would be interested if you could plug it into your app and see if anything breaks (it will be a branch)... its not totally ready yet, but so far all the apps ive tested work (including UTs) - but would defo be interested in feedback
yeah that's fine with me
branch experimental/better_query_cache in e2
(need latest db-core)
Anything i should be weary of?
nope, nothing should be different functionally, but you should see significant perf improvements, especially if your db is remote (which it is for you)
(doesnt fix the orphans, just significant improvments in the query cache - which was totally broken ๐ฎ )
it looks to build up a list of ids "it will need" and then caches the results, it also (now) does some clever things to reduce queries that can be reconstructed from the cache
yes, but only per call
so you will still always get "live" data, its just if a call wants "id 1" three times in the same call stack, it will get the other 2 from the cache
the cache has always been there - it was just broken, but now its a) not broken and b) much smarter (it splits queries and allows cached results to be "reconstructued" from previously cached results with a different query... also, another big feature is it will "pre cache" queries... so it will first (again, per call) look to collate ids, and make a single query with all the ids (and cache the result, as well as the "split" results)
shouldnt make a difference, but mainly just make sure you app works the same
oh that's another cool thing, i recently learnt about that when performing some db backups and was curious about how it structured the data
will git everything up and switch the branch
the main think im interested in is that everything works exactly the same, id also be interested in if there is a perf increase in your app since you talk remote to the db, so latency "per query" is serious
would be quite curious if it did help, i think i've managed to make it relatively "nice" for the most part
Will see if I can get some timings there
yeah, fieldset will still help ofc by totally just not making queries, but e2 makes SIGNIFICANTLY less queries now
i had a totally non trivial test (lots of relationships, very deep structure) and it went from 181 queries to 10 (granted the query cache was totally broken with the 181 version and i hadnt realised)
so if you assume 10ms latency per query (which isnt unreasonable) thats 1.8 seconds in just network comms, not even running the queries
realistically in my apps, i never talk remote to the db - its via an api, so no network lag - but thats not the point... 181 queries is INSANE
Fieldset:
Account(main) Loaded: 0.174 seconds
Account(Test1) Loaded: 0.135 seconds
Account(Test2) Loaded: 0.158 seconds
Account(Test3) Loaded: 0.141 seconds
Account(Test4) Loaded: 0.14 seconds
Account(Test5) Loaded: 0.139 seconds
No Fieldset:
Account(main) Loaded: 0.904 seconds
Account(Test1) Loaded: 2.343 seconds
Account(Test2) Loaded: 0.581 seconds
Account(Test3) Loaded: 1.625 seconds
Account(Test4) Loaded: 0.151 seconds
Account(Test5) Loaded: 0.893 seconds
So here's some befores
oh, nice one... will defo be interesting to see the "afters"
Fieldset:
Account(main) Loaded: 0.259 seconds
Account(Test1) Loaded: 0.241 seconds
Account(Test2) Loaded: 0.223 seconds
Account(Test3) Loaded: 0.25 seconds
Account(Test4) Loaded: 0.2 seconds
Account(Test5) Loaded: 0.225 seconds
No Fieldset:
Account(main) Loaded: 0.288 seconds
Account(Test1) Loaded: 0.296 seconds
Account(Test2) Loaded: 0.235 seconds
Account(Test3) Loaded: 0.321 seconds
Account(Test4) Loaded: 0.193 seconds
Account(Test5) Loaded: 0.254 seconds
Pretty good ๐
slower with fieldsets though, which seems odd
only just clocked that
was comparing to no fieldset
maybe fieldset is ignored in places with latest version?
i dont think so... but ill double check... maybe i missed something on fieldset... dont think i did though
timings are a lot more consistent overall though
yeah, its defo better... and more importantly, app works exactly the same?
app seems to be working fine at first glance
perfect
no errors or anything obviously wrong
not sure what would happen if something updated something in the cache and i read from the same query or cases like that yet though
what cache? e2 query cache?
yea, like backend updates a value in the db
it wont matter... its restricted per call, the cache isnt global or anything, its destroyed after use (ie, after the call "find" for example, is complete)
ohhhh, that's neat
eg:
findByIdFn.code += macro @:privateAccess {
var queryCacheId = entities.EntityManager.instance.generateQueryCachedId();
if (fieldSet == null) fieldSet = new entities.EntityFieldSet();
return new promises.Promise((resolve, reject) -> {
init().then(_ -> {
return findInternal(query, queryCacheId, fieldSet, 1);
}).then(entitiesList -> {
entities.EntityManager.instance.clearQueryCache(queryCacheId);
resolve(entitiesList[0]);
}, error -> {
entities.EntityManager.instance.clearQueryCache(queryCacheId);
reject(error);
});
});
}
queryCacheId stuff - it gets passed around and the cache only applies to that call ("find" in this case)
that's really nice
no need to get the same account on all results
basically makes fieldset a bit useless ๐
exactly, and the precache is smarter now... so it will build up a query like "select * from t where x in (1, 2, 5, 7, 8)"... then it will cache this result, but the best part is that its all the query ast in db-core, so i can then split that into individual cache entries for 1, 2, 5, 7, 8... and then later if another query comes through with "select * from t where x in (1, 2, 8)" (which is also a query expr) then i can say "oh, i have all those individual ids cached already, and can "reconstruct" a recordset from those ids
not really, feildsets were never supposed to be a perf enhancement, they are there so you can stop certain things from being populated (like sensitive db data)... previously in entities1, you would have to do something like user.passwordHash = null (if you were sending the entity to the client via serialization)... even worse in e1 if it was a sub object... eg: foo.bar.user.passwordHash = null... in e2 you use fieldsets like User.NoSensitiveInfo
when you get a moment, can you pull e2 (branch) and retry? I wasnt honouring fieldsets in the query precache, so it was building up ids for things it would never query (because of fieldsets)
Account(main) Loaded: 0.222 seconds
Account(Test1) Loaded: 0.183 seconds
Account(Test2) Loaded: 0.179 seconds
Account(Test3) Loaded: 0.2 seconds
Account(Test4) Loaded: 0.168 seconds
Account(Test5) Loaded: 0.179 seconds
that's with fieldsets now
same ish...
dont really understand how it can be slower than the original version though... you sure you pulled latest... maybe network variance
no more things to pull
but i'll do another test in a bit, need to do some stuff first tho
Account(main) Loaded: 0.133 seconds
Account(Test1) Loaded: 0.203 seconds
Account(Test2) Loaded: 0.176 seconds
Account(Test3) Loaded: 0.193 seconds
Account(Test4) Loaded: 0.128 seconds
Account(Test5) Loaded: 0.149 seconds
maybe recompile didn't occur from the last one because these are better on average
2nd one being higher is odd
ohh... i did a mistake ๐คฆโโ๏ธ
need to reprocess the data
OK, thats seems better though
little bit of variance isnt super surprising, but they are all less than the "non optimized" version
what sort of load is this out of curiosity? Like, how many records (ish) - including relationships etc
it's quite a good stress test all around, even for my account sync setup
relationships vary from position to position
will give some details after reprocessing the data
** No Fieldset**
Account(main) Total Records=35 Total Orders=78 Time: 0.312 seconds
Account(Test1) Total Records=112 Total Orders=225 Time: 0.385 seconds
Account(Test2) Total Records=21 Total Orders=44 Time: 0.237 seconds
Account(Test3) Total Records=77 Total Orders=155 Time: 0.312 seconds
Account(Test4) Total Records=1 Total Orders=2 Time: 0.257 seconds
Account(Test5) Total Records=37 Total Orders=74 Time: 0.259 secondsFieldset
Account(main) Total Records=35 Total Orders=0 Time: 0.219 seconds
Account(Test1) Total Records=112 Total Orders=0 Time: 0.188 seconds
Account(Test2) Total Records=21 Total Orders=0 Time: 0.184 seconds
Account(Test3) Total Records=77 Total Orders=0 Time: 0.203 seconds
Account(Test4) Total Records=1 Total Orders=0 Time: 0.179 seconds
Account(Test5) Total Records=37 Total Orders=0 Time: 0.206 seconds
each order/record will have a reference to an account as well
and account will reference a user
cool - so i think im going to merge this into main pretty soon... all my tests are good, timings dont improve drastically (because its all localhost effectively) but query count defo does
yeah man it's super cool how much it's reduced
it would basically deadlock the page
for seconds in some cases
now it's basically nothing
i would still advise that dont call directly into the db (ever! nothing to do with e2)... but thats not the point... 181 queries vs 4-5 is crazy... granted not trivial relationship wise... but thats defo NOT the point
wdym? (want to make sure I understand the advice)
i mean that the db access should be behind an api layer... if you had a "findUsers" kinda thing, you wouldnt want that talking to the db directly, you would want that exposed as an api function, and that api function would talk to the db... that api layer would include the security, etc
atm, when your app wants "orders" its talking direct to the db asking for them - the reason why i never noticed the latency is because the client talks to the api and that talks to the db which is "0" network latency, so it just never came up the huge amount of queries it was creating
that's the ultimate plan
whats "crazy" is once i plugged my local app into a remote db (which is bad mojo) is saw stupid stupid times... ...
so for sure, e2 (well, the query cache) was bugged to hell
not the sort of thing i can reliably unit test either... well, not true-ish, i have a few unit tests now for the query count and the query cache hit count... so if something gets better or worse, these UTs will fail
i think you defo want that pattern / abstraction... direct db access is bad (unless its a local app, but even then i would argue it cant scale)... but, in this specific case it led me down a path of bugs and "i dont like this, thats crazy, that many queries?!?!" ๐
with the way I've developed my app switching out to an api isn't that big of an issue
nice, sounds like you already abstracted out the data layer on the client ๐
so I'm happy to dev with direct access, and I'm gonna be the primary user and tester anyway for the foreseeable future so no need to worry about the api abstraction for now
I'm sure some will bundle sql with their apps tho so still useful
i use a pattern like: var client = RpcClient<ISomeApi>()... then the macro builds "all the parts"... best part is that macros also build the (dynamic) parts of the server also
lol
so if you change the api, then server or client will fail to build
magic ๐
yeah, its actually really nice, i want to extract it at some point and make it a little more generic... its fairly generic, but i feel like it probably has some shortcuts in it for glint specifically (i dont remember any, but im assuming)... but the client -> server comms is a dream (over any transport, http, websockets, files if you wanted, its all pluggable)
(trying to find an example to gush about ๐ )
@:expose @:keep
@:operation(glint.api.users.UsersApi.findUsers)
class FindUsers implements IRpcServerOperation {
private static var log = new Logger("glint.api.users.server.operations.FindUsers");
public function execute(criteria:FindUsersCriteria, requestor:UserData):Promise<Array<UserData>> {
return new Promise((resolve, reject) -> {
log.info('finding users, criteria: ${criteria} (requestor: ${requestor != null ? requestor.username : null})');
var queryParts:Array<QueryExpr> = [];
if (criteria.name != null) {
queryParts.push(Query.query($username = criteria.name));
}
if (criteria.searchTerm != null) {
queryParts.push(Query.query($username =~ criteria.searchTerm));
}
var query = Query.joinQueryParts(queryParts, QBinop.QOpBoolAnd);
UserData.findAll(query, criteria.pageSize, UserData.NoSensitiveInformation).then(users -> {
resolve(users);
}, error -> {
reject(error);
});
});
}
}
thats the server operation, that gets built into a js (basically) and gets dynamically loaded (based on some config), all the endpoints are created, etc...
ohh it plugs with entities
entities, serializers and rpc (which isnt released)
call is just:
ApiManager.instance.users.findUsers({searchTerm: "*" + searchTermTextField.text + "*", pageSize:ITEMS_LIMIT_PER_TYPE}, null).then
quite alot of "magic" going on client side to be fair
i feel like ive always been searching for the "holy grail" of client / server comms... and, maybe it will change, but this pattern is insanely productive... you write a server side function, and it all kinda just gets "linked up"
i feel like at this point core-haxe is an entire web framework lol
to be fair, its just how you put it all together... like, this "RpcOperation" is using core-haxe/modular... didnt need to, but its perfect... the operations are loaded by the server, it creates the endpoint (well, the rpc system does) and it all just slots in, under the hood its using http (or in the future websockets, and could flip between them)... but the libs themselves are totally standalone
no part of entities, http, rpc, logging, etc is required for the other
actually, maybe logging and promises... thats probably it though
also... ... ... ... not a web framework ๐ (could fairly easily create a similar thing with hxwidgets and the same system - modular would be an issue there, but when i have more time, i know it would all work via cppia)
@velvet lodge what server backend are you building towards?
currently im targeting node, but i think all the libs (with the exception of modular) should work as is on most haxe targets
loaded up a page on linux and things were taking a long time, thought i had broke the app
turns out i just forgot to sync the e2 branch change x)
thats in haxe.Http... though its shadowed in core-haxe/http... why wouldnt you have things encoded anyway?
i'm signing something and its interfering with the exchange validating the signed request due to mismatching url
looks like it does similar in "non-shadowed" haxe: https://github.com/HaxeFoundation/haxe/blob/development/std/haxe/http/HttpJs.hx#L127
yeah, its defo a std thing
its pretty standard / important i think
why dont you urlEncode your request url so they match?
its typically an optional thing
ive never heard of it being optional personally... but maybe i just havent come across it
i assumed exchange would expect its page token in original form but I guess I can verify
yeah encoding the string causes other issues
realistically it makes sense
if i'm encrypting a string that needs to be in a certain format, and the validation string in the url is different then its just rejecting the string
@velvet lodge I did manage to solve it by making encoding optional, you cool with a pr, i think its done in a non breaking way
oh no...
You don't shadow httpbase ๐ค
adjusted the pr
I have an alternative change i'd like to propose as well for httpresponse
class HttpResponse<T:Dynamic = Dynamic> {
public var bodyAsJson(get, null):T;
private function get_bodyAsJson():T {
if (body == null) {
return null;
}
return Json.parse(body.toString());
}
}
It is haxe > 4.3 syntax i believe so can conditionally wrap it but it's a nice change none the less I think
if no type parameter is passed it just defaults to Dynamic
yeah fails on haxe < 4.3
would it not be better as a #if?
how would you use this though? I mean, how would supply the type param?
Not all cases require one or the other, node has it as an optional parameter
client.post().then(function(resp:HttpResponse<MyType>){})
on phone atm so may not be exactly correct but I'm using it like that locally and it works fine
new addition: https://github.com/core-haxe/observable
still wip, need more test coverage, but got bored, working as expected with manual tests
basic usage:
class ObservableObject1 implements IObservable {
public var intVar:Int = 10;
public var objectArray:Array<ObservableObject1>;
public var subObject:ObservableObject1;
public var normalArray:Array<Int>;
public var objectMap:Map<String, ObservableObject1>;
public var normalMap:Map<String, Int>;
}
ObservableDefaults.GroupChanges = true; // defaults to true
ObservableDefaults.EliminateDuplicates = true; // defaults to true
var o1 = new ObservableObject1();
o1.registerChangeListener((changes) -> {
...
});
o1.intVar = 101;
o1.intVar = 101;
o1.intVar = 102;
o1.subObject = new ObservableObject1();
o1.subObject.intVar = 201;
o1.objectArray = [];
o1.objectArray.push(new ObservableObject1());
o1.objectArray[0].intVar = 505;
o1.objectArray[0].subObject = new ObservableObject1();
o1.objectArray[0].subObject.intVar = 606;
o1.objectArray[0].subObject.intVar = 607;
o1.objectArray[0].subObject.intVar = 608;
o1.objectArray[0].subObject.intVar = 609;
o1.objectArray[0].subObject.objectArray = [new ObservableObject1()];
o1.objectArray[0].subObject.objectArray[0].intVar = 707;
//o1.objectMap = []; // wont work, need to init as map, or use ObservableMap explicitly
o1.objectMap = ["one" => new ObservableObject1()];
o1.objectMap.get("one").intVar = 808;
o1.objectMap.get("one").subObject = new ObservableObject1();
o1.objectMap.get("one").subObject.intVar = 909;
o1.objectMap.get("one").subObject.objectArray = [new ObservableObject1(), new ObservableObject1(), new ObservableObject1()];
o1.objectMap.get("one").subObject.objectArray[1].intVar = 333;
o1.normalArray = [];
o1.normalArray.push(1111);
o1.normalMap = ["tim" => 2222];
o1.normalMap.set("tim", 3333);
( โ๏ธ that example there leads to a single invocation of the registerChangeListener callback with a list of 24 changes)
Does entities/dbcore support haxe enums as a type?
enum Foo {
Color(r:Int, g:Int, b:Int);
}```
Kind of deal
it doesnt, not currently - i would like to add them, but it wouldnt be trivial
yeah seems like a pretty powerful addition
i think you could / would just tread them like any other entity (ish)..
so you would have a "color" table, with id, r, g, b... and that would link to the instance var (by id)... which is how 1->1 entities work
so i think the general system is already there... its "just" doing it, and all that entails
Oh interesting
that's a cool way to do it
i thought it would just get serialised/jsonified ๐
https://github.com/HaxeFoundation/haxe/issues/12369#issuecomment-3311588934 Might be useful for you to read
OK, intersting about the rejection, so i guess i can do a "if error is MySqlError"... doesnt fix the underlying problem though, correct? Just better error handling
yeah, underlying problem is still in haxe
import shared.dbtypes.User;
import entities.IEntity;
class SessionToken implements IEntity {
public var user:User;
public var token:String;
public var created:Float;
public var expires:Float;
public var lastActive:Float;
public var ip:String;
public var name:String;
}
class User implements IEntity {
public var username:String;
public var password:String;
public var passwordSalt:String;
public var created:Float;
public var lastLoggedIn:Float;
}
Am I doing something wrong here? this seems correct, but I'm getting:
mysql/impl/cpp/DatabaseConnection.hx:163: UKNONWN:,TClass(shared.dbtypes.User)
@smoky crane (pinging cause you probably aren't in the thread anymore ๐)
valid, defo wasnt ๐