diff --git a/Changelog.markdown b/Changelog.markdown
--- a/Changelog.markdown
+++ b/Changelog.markdown
@@ -1,3 +1,7 @@
+# 2023-07-18 (v0.9.7)
+
+* fix critical bug resulting in empty results from cross joins
+	
 # 2022-11-05 (v0.9.6)
 
 * fix tuple context passed down to extended expressions
diff --git a/project-m36.cabal b/project-m36.cabal
--- a/project-m36.cabal
+++ b/project-m36.cabal
@@ -1,6 +1,6 @@
 Cabal-Version: 2.2
 Name: project-m36
-Version: 0.9.6
+Version: 0.9.7
 License: MIT
 --note that this license specification is erroneous and only labeled MIT to appease hackage which does not recognize public domain packages in cabal >2.2- Project:M36 is dedicated to the public domain
 Build-Type: Simple
@@ -213,7 +213,6 @@
                    TutorialD.Interpreter.Types,
                    TutorialD.Interpreter.SchemaOperator,
                    TutorialD.Interpreter.TransGraphRelationalOperator,
-                   TutorialD.Interpreter.SchemaOperator,
                    TutorialD.Printer
     main-is: TutorialD/tutd.hs
     CC-Options: -fPIC
@@ -384,7 +383,7 @@
     import: commontest
     type: exitcode-stdio-1.0
     main-is: benchmark/OnDiskClient.hs
-    GHC-Options: -rtsopts -Wall -threaded -eventlog -O2 -finfo-table-map -fdistinct-constructor-tables
+    GHC-Options: -rtsopts -Wall -threaded -eventlog -finfo-table-map -fdistinct-constructor-tables
 
 Test-Suite test-server
     import: commontest
diff --git a/src/lib/ProjectM36/Persist.hs b/src/lib/ProjectM36/Persist.hs
--- a/src/lib/ProjectM36/Persist.hs
+++ b/src/lib/ProjectM36/Persist.hs
@@ -43,8 +43,6 @@
 #if defined(mingw32_HOST_OS)
 import ProjectM36.Win32Handle
 #else
--- maybe this could be "safe" for GC since we only use it with withCString
--- https://www.reddit.com/r/haskell/comments/xlm4qv/haskell_ffi_call_safety_and_garbage_collection/
 foreign import ccall unsafe "cDirectoryFsync" cHSDirectoryFsync :: CString -> IO CInt
 #endif
 
diff --git a/src/lib/ProjectM36/Tuple.hs b/src/lib/ProjectM36/Tuple.hs
--- a/src/lib/ProjectM36/Tuple.hs
+++ b/src/lib/ProjectM36/Tuple.hs
@@ -113,7 +113,7 @@
 -- if there are shared attributes, if they match, create a new tuple from the atoms of both tuples based on the attribute ordering argument
 singleTupleJoin :: Attributes -> RelationTuple -> RelationTuple -> Either RelationalError (Maybe RelationTuple)
 singleTupleJoin joinedAttrs tup1@(RelationTuple tupAttrs1 _) tup2@(RelationTuple tupAttrs2 _) = if
-  V.null keysIntersection || atomsForAttributeNames keysIntersection tup1 /= atomsForAttributeNames keysIntersection tup2
+  atomsForAttributeNames keysIntersection tup1 /= atomsForAttributeNames keysIntersection tup2
   then
     return Nothing
   else
diff --git a/test/TutorialD/InterpreterTest.hs b/test/TutorialD/InterpreterTest.hs
--- a/test/TutorialD/InterpreterTest.hs
+++ b/test/TutorialD/InterpreterTest.hs
@@ -92,7 +92,8 @@
       testExtendProcessorTuplePushdown,
       testDDLHash,
       testShowDDL,
-      testRegisteredQueries
+      testRegisteredQueries,
+      testCrossJoin
       ]
 
 simpleRelTests :: Test
@@ -846,4 +847,20 @@
   Right () <- executeDatabaseContextExpr sessionId dbconn (RemoveRegisteredQuery "protect_x")
   Right () <- executeDatabaseContextExpr sessionId dbconn (Undefine "x")  
   pure ()
+  
+testCrossJoin :: Test
+testCrossJoin = TestCase $ do
+  (session, dbconn) <- dateExamplesConnection emptyNotificationCallback
+  -- Athens, London, Paris cross joined with 17, 12, 14, 19
+  executeTutorialD session dbconn "x:=(s{city}) join (s{status})"
+  eActual <- executeRelationalExpr session dbconn (RelationVariable "x" ())
+  let eExpected = mkRelationFromList (attributesFromList [Attribute "city" TextAtomType, Attribute "status" IntegerAtomType]) [[city,status] | city <- map TextAtom ["Athens", "London", "Paris"], status <- map IntegerAtom [30,20,10]]
+  assertBool "cross join error" (isRight eActual)
+  assertEqual "cross join" eExpected eActual
+
+  executeTutorialD session dbconn "y:=relation{tuple{a 1},tuple{a 2},tuple{a 3}} join relation{tuple{b 4}, tuple{b 5}, tuple{b 6}}"
+  let eExpected' = mkRelationFromList (attributesFromList [Attribute "a" IntegerAtomType, Attribute "b" IntegerAtomType]) [[a,b] | a <- map IntegerAtom [1,2,3], b <- map IntegerAtom [4,5,6]]
+  eActual' <- executeRelationalExpr session dbconn (RelationVariable "y" ())
+  assertBool "cross join 2 error" (isRight eActual')  
+  assertEqual "cross join 2" eExpected' eActual'
   
