FYI, used a workaround: TowergameDao >> findStateByAgent: anAgent | workaround | workaround := anAgent ifNotNil: [ ByteArray withAll: anAgent id ]. ^ self glorpSession readOneOf: TgState where: [ :one | one agent id = workaround ] But it is _ugly_ (though, it actually generates the same short SQL; it was RelationExpression >> condensePrimaryKeyComparison which inspired me to do this). I am sure one of the points of Glorp is to be able to write the original: findStateByAgent: anAgent ^ self glorpSession readOneOf: TgState where: [ :one | one agent = anAgent ] Is it true (should this work)? Herby Herby VojÄÃk wrote:
Esteban A. Maringolo wrote:
Do you have the code somewhere loadable? Reading chunk is something I do only when everything crashed :D Esteban A. Maringolo
I will attach the .st files... not loadable as its in private on-premise git repo :-(
Thank you very much,
Herby
2017-08-14 13:44 GMT-03:00 Herby VojÄÃk<herby@mailbox.sk>:
Hello!
I encountered a problem with OneToOneMapping and type coercion. When writing data, thing work; when reading data, the right child of relation fails to convert.
I tried everything possible to inject converters (even subclassing GlorpBlobType), but to no avail. RelationExpression passes conversion to its left child:
convertedDbValueOf: anObject "Assume that our types match, so we can ask either child to do the conversion. That isn't guaranteed, but should at least work for the common cases." ^leftChild convertedDbValueOf: anObject.
but the left child is FieldExpression in case of OneToOneMapping, which:
convertedDbValueOf: anObject "We don't do any conversion" ^anObject
What is strange, writing works (even the OneToOneMapping, I opened the sqlite file with an explorer), but second SELECT, one using the relation (`state := self dao findStateByAgent: agent` in clientSync), fails with "GlorpDatabaseReadError: Could not coerce arguments". FWIW, the first one _does_ convert when creating bindings, as it uses MappingExpression as left child (stepped over it in debugger).
Is it meant to be a strange case that primary key is something non-primitive needing coercion (in this case, it is a UUID which needs coercion to ByteArray, even if it is its subclass)?
Here's the stack of running the test which fails:
PharoDatabaseAccessor(DatabaseAccessor)>>handleError:for: [ :ex | self handleError: ex for: command ] in [ | result | self checkPermissionFor: command. result := [ (self useBinding and: [ command useBinding ]) ifTrue: [ command executeBoundIn: self ] ifFalse: [ command executeUnboundIn: self ] ] on: Dialect error do: [ :ex | self handleError: ex for: command ]. aBoolean ifTrue: [ result ] ifFalse: [ result upToEnd ] ] in PharoDatabaseAccessor(DatabaseAccessor)>>executeCommand:returnCursor: BlockClosure>>cull: Context>>evaluateSignal: Context>>handleSignal: Error(Exception)>>signal Error(Exception)>>signal: ExternalLibraryFunction(Object)>>error: ExternalLibraryFunction(Object)>>externalCallFailed ExternalLibraryFunction(ExternalFunction)>>invokeWithArguments: UDBCSQLite3Library>>apiBindBlob:atColumn:with:with:with: UDBCSQLite3Library>>with:at:putBlob: UDBCSQLite3Statement>>at:putByteArray: UDBCSQLite3ResultSet>>execute:withIndex:withValue: [ :v | i := self execute: statement withIndex: i withValue: v ] in UDBCSQLite3ResultSet>>execute:withCollection: OrderedCollection>>do: UDBCSQLite3ResultSet>>execute:withCollection: UDBCSQLite3ResultSet>>execute:with:on: UDBCSQLite3Connection>>execute:with: GlorpSQLite3Driver>>basicExecuteSQLString:binding: PharoDatabaseAccessor>>executeCommandBound: QuerySelectCommand(DatabaseCommand)>>executeBoundIn: [ (self useBinding and: [ command useBinding ]) ifTrue: [ command executeBoundIn: self ] ifFalse: [ command executeUnboundIn: self ] ] in [ | result | self checkPermissionFor: command. result := [ (self useBinding and: [ command useBinding ]) ifTrue: [ command executeBoundIn: self ] ifFalse: [ command executeUnboundIn: self ] ] on: Dialect error do: [ :ex | self handleError: ex for: command ]. aBoolean ifTrue: [ result ] ifFalse: [ result upToEnd ] ] in PharoDatabaseAccessor(DatabaseAccessor)>>executeCommand:returnCursor: BlockClosure>>on:do: [ | result | self checkPermissionFor: command. result := [ (self useBinding and: [ command useBinding ]) ifTrue: [ command executeBoundIn: self ] ifFalse: [ command executeUnboundIn: self ] ] on: Dialect error do: [ :ex | self handleError: ex for: command ]. aBoolean ifTrue: [ result ] ifFalse: [ result upToEnd ] ] in PharoDatabaseAccessor(DatabaseAccessor)>>executeCommand:returnCursor: [ caught := true. self wait. blockValue := mutuallyExcludedBlock value ] in Semaphore>>critical: BlockClosure>>ensure: Semaphore>>critical: PharoDatabaseAccessor(DatabaseAccessor)>>executeCommand:returnCursor: [ session accessor executeCommand: command returnCursor: true ] in SimpleQuery>>rowsFromDatabaseWithParameters: BlockClosure>>on:do: SimpleQuery>>rowsFromDatabaseWithParameters: SimpleQuery(AbstractReadQuery)>>readFromDatabaseWithParameters: SimpleQuery(AbstractReadQuery)>>executeWithParameters:in: GlorpSession>>execute: GlorpSession>>readOneOf:where: TowergameDao>>findStateByAgent: [ | agent state | agent := self dao findAgentById: anObject agentId. state := self dao findStateByAgent: agent. ^ NeoJSONObject new agentId: agent id; stateVersion: state version; totalAnsweredQuestions: (NeoJSONObject new good: 0; bad: 0; yourself); yourself ] in Towergame>>clientSync: [ myUnitOfWork := self hasUnitOfWork not. myUnitOfWork ifTrue: [ self beginUnitOfWork ]. result := aBlock numArgs = 1 ifTrue: [ aBlock value: self ] ifFalse: [ aBlock value ]. myUnitOfWork ifTrue: [ self commitUnitOfWork ] ] in GlorpSession>>inUnitOfWorkDo: BlockClosure>>ifCurtailed: GlorpSession>>inUnitOfWorkDo: TowergameDao>>inUnitOfWorkDo: Towergame>>clientSync: TowergameSyncTests>>testPlayerChecksStateVersion TowergameSyncTests(TestCase)>>performTest [ self setUp. self performTest ] in TowergameSyncTests(TestCase)>>runCase BlockClosure>>ensure: TowergameSyncTests(TestCase)>>runCase [ aTestCase runCase ] in [ [ aTestCase runCase ] on: Halt do: [ :halt | "if test was halted we should resume all background failures to debug all of them together with test process" failedProcesses keysDo: #resume. halt pass ] ] in TestExecutionEnvironment>>runTestCaseSafelly: BlockClosure>>on:do: [ [ aTestCase runCase ] on: Halt do: [ :halt | "if test was halted we should resume all background failures to debug all of them together with test process" failedProcesses keysDo: #resume. halt pass ] ] in TestExecutionEnvironment>>runTestCaseSafelly: BlockClosure>>on:do: TestExecutionEnvironment>>runTestCaseSafelly: [ self runTestCaseSafelly: aTestCase ] in [ [ self runTestCaseSafelly: aTestCase ] ensure: [ testCompleted := true. watchDogSemaphore signal ]. "signal that test case completes" self checkForkedProcesses ] in TestExecutionEnvironment>>runTestCase: BlockClosure>>ensure: [ [ self runTestCaseSafelly: aTestCase ] ensure: [ testCompleted := true. watchDogSemaphore signal ]. "signal that test case completes" self checkForkedProcesses ] in TestExecutionEnvironment>>runTestCase: BlockClosure>>ifCurtailed: TestExecutionEnvironment>>runTestCase: [ testEnv runTestCase: aTestCase ] in DefaultExecutionEnvironment>>runTestCase: [ self value: anExecutionEnvironment. anExecutionEnvironment activated. aBlock value ] in CurrentExecutionEnvironment class>>activate:for: BlockClosure>>ensure: CurrentExecutionEnvironment class>>activate:for: TestExecutionEnvironment(ExecutionEnvironment)>>beActiveDuring: DefaultExecutionEnvironment>>runTestCase: CurrentExecutionEnvironment class>>runTestCase: TowergameSyncTests(TestCase)>>runCaseManaged [ aTestCase announce: TestCaseStarted withResult: self. aTestCase runCaseManaged. aTestCase announce: TestCaseEnded withResult: self. self addPass: aTestCase ] in TestResult>>runCaseForDebug: BlockClosure>>on:do: TestResult>>runCaseForDebug: [ result runCaseForDebug: self ] in TowergameSyncTests(TestCase)>>debug BlockClosure>>ensure: TowergameSyncTests(TestCase)>>debug [ :each | each debug. self announceTest: each. self changed: each ] in [ self tests do: [ :each | each debug. self announceTest: each. self changed: each ] ] in TestSuite>>debug OrderedCollection>>do: [ self tests do: [ :each | each debug. self announceTest: each. self changed: each ] ] in TestSuite>>debug BlockClosure>>ensure: TestSuite>>debug [ :aSuite | aSuite debug ] in TestRunner>>debugSuite: BlockClosure>>cull: BlockClosure>>cull:cull: [ aBlock cull: aTestSuite cull: result ] in TestRunner>>executeSuite:as: BlockClosure>>ensure: TestRunner>>executeSuite:as: TestRunner>>debugSuite: TestRunner>>debug: TestRunner>>errorSelected: PluggableListMorph>>changeModelSelection: PluggableListMorph>>mouseUpOnSingle: PluggableListMorph>>mouseUp: PluggableListMorph(Morph)>>handleMouseUp: MouseButtonEvent>>sentTo: PluggableListMorph(Morph)>>handleEvent: MorphicEventDispatcher>>dispatchDefault:with: MorphicEventDispatcher>>handleMouseUp: MouseButtonEvent>>sentTo: [ ^ anEvent sentTo: self ] in MorphicEventDispatcher>>dispatchEvent:with: BlockClosure>>ensure: MorphicEventDispatcher>>dispatchEvent:with: PluggableListMorph(Morph)>>processEvent:using: PluggableListMorph(Morph)>>processEvent: PluggableListMorph>>handleFocusEvent: [ ActiveHand := self. ActiveEvent := anEvent. result := focusHolder handleFocusEvent: (anEvent transformedBy: (focusHolder transformedFrom: self)) ] in HandMorph>>sendFocusEvent:to:clear: BlockClosure>>on:do: WorldMorph(PasteUpMorph)>>becomeActiveDuring: HandMorph>>sendFocusEvent:to:clear: HandMorph>>sendEvent:focus:clear: HandMorph>>sendMouseEvent: HandMorph>>handleEvent: HandMorph>>processEventsFromQueue: HandMorph>>processEvents [ :h | self activeHand: h. h processEvents. self activeHand: nil ] in WorldState>>doOneCycleNowFor: Array(SequenceableCollection)>>do: WorldState>>handsDo: WorldState>>doOneCycleNowFor: WorldState>>doOneCycleFor: WorldMorph>>doOneCycle WorldMorph class>>doOneCycle [ [ WorldMorph doOneCycle. Processor yield. false ] whileFalse: [ ] ] in MorphicUIManager>>spawnNewProcess [ self value. Processor terminateActive ] in BlockClosure>>newProcess