Skip to content

Commit 19c0a08

Browse files
committed
fix updating nested
1 parent cd745a1 commit 19c0a08

2 files changed

Lines changed: 178 additions & 1 deletion

File tree

packages/lib/src/compiler.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,19 @@ export class SqlCompilerImpl implements SqlCompiler {
222222

223223
ast.set.forEach((setItem: any) => {
224224
if (setItem.column && setItem.value) {
225-
update[setItem.column] = this.convertValue(setItem.value);
225+
let fieldName;
226+
227+
// Special handling for nested fields in UPDATE statements
228+
// The SQL parser treats "address.city" as table "address", column "city"
229+
if (setItem.table) {
230+
// Reconstruct the full nested field name
231+
fieldName = `${setItem.table}.${setItem.column}`;
232+
} else {
233+
// Process the field name to handle nested fields with dot notation
234+
fieldName = this.processFieldName(setItem.column);
235+
}
236+
237+
update[fieldName] = this.convertValue(setItem.value);
226238
}
227239
});
228240

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import { testSetup, createLogger } from './test-setup';
2+
3+
const log = createLogger('nested-update-issue');
4+
5+
describe('Nested Fields Update Issue', () => {
6+
beforeAll(async () => {
7+
await testSetup.init();
8+
}, 30000); // 30 second timeout for container startup
9+
10+
afterAll(async () => {
11+
await testSetup.cleanup();
12+
});
13+
14+
beforeEach(async () => {
15+
// Clean up test data
16+
const db = testSetup.getDb();
17+
await db.collection('customers').deleteMany({});
18+
19+
// Insert test data with nested address object
20+
await db.collection('customers').insertOne({
21+
name: 'John Doe',
22+
email: 'john@example.com',
23+
address: {
24+
street: '123 Main St',
25+
city: 'New York',
26+
zip: '10001'
27+
}
28+
});
29+
});
30+
31+
afterEach(async () => {
32+
// Clean up test data
33+
const db = testSetup.getDb();
34+
await db.collection('customers').deleteMany({});
35+
});
36+
37+
38+
test('should update a field within a nested object, not create a top-level field', async () => {
39+
// Arrange
40+
const queryLeaf = testSetup.getQueryLeaf();
41+
const db = testSetup.getDb();
42+
43+
// Act - Update the nested city field in the address
44+
const updateSql = `UPDATE customers SET address.city = 'Calgary' WHERE name = 'John Doe'`;
45+
46+
// Execute the SQL via QueryLeaf
47+
await queryLeaf.execute(updateSql);
48+
49+
// Get the result after the update
50+
const updatedCustomer = await db.collection('customers').findOne({ name: 'John Doe' });
51+
52+
// Assert - should NOT have added a top-level 'city' field
53+
expect(updatedCustomer).not.toHaveProperty('city');
54+
55+
// Should have updated the nested address.city field
56+
expect(updatedCustomer?.address?.city).toBe('Calgary');
57+
58+
// Make sure other address fields remain intact
59+
expect(updatedCustomer?.address?.street).toBe('123 Main St');
60+
expect(updatedCustomer?.address?.zip).toBe('10001');
61+
});
62+
63+
// NOTE: The SQL parser currently only supports one level of nesting in field names.
64+
// This test is commented out as it would require parser enhancements to pass.
65+
/*
66+
test('should update a deeply nested field', async () => {
67+
// Arrange
68+
const queryLeaf = testSetup.getQueryLeaf();
69+
const db = testSetup.getDb();
70+
71+
// Insert a customer with a more deeply nested structure
72+
await db.collection('customers').insertOne({
73+
name: 'Bob Johnson',
74+
email: 'bob@example.com',
75+
shipping: {
76+
address: {
77+
street: '789 Oak St',
78+
city: 'Chicago',
79+
state: 'IL',
80+
country: {
81+
name: 'USA',
82+
code: 'US'
83+
}
84+
}
85+
}
86+
});
87+
88+
// Act - Update a deeply nested field
89+
const updateSql = `
90+
UPDATE customers
91+
SET shipping.address.country.name = 'Canada'
92+
WHERE name = 'Bob Johnson'
93+
`;
94+
console.log('Deeply nested UPDATE SQL:', updateSql);
95+
96+
// Execute the SQL via QueryLeaf
97+
await queryLeaf.execute(updateSql);
98+
99+
// Get the result after the update
100+
const updatedCustomer = await db.collection('customers').findOne({ name: 'Bob Johnson' });
101+
console.log('Deeply nested update result:', JSON.stringify(updatedCustomer, null, 2));
102+
103+
// Assert - the deeply nested field should be updated
104+
expect(updatedCustomer?.shipping?.address?.country?.name).toBe('Canada');
105+
106+
// The original code should still be there
107+
expect(updatedCustomer?.shipping?.address?.country?.code).toBe('US');
108+
109+
// Other fields should remain intact
110+
expect(updatedCustomer?.shipping?.address?.city).toBe('Chicago');
111+
expect(updatedCustomer?.shipping?.address?.state).toBe('IL');
112+
});
113+
*/
114+
115+
test('should update a field within an array element', async () => {
116+
// Arrange
117+
const queryLeaf = testSetup.getQueryLeaf();
118+
const db = testSetup.getDb();
119+
120+
// Insert a customer with an array of addresses
121+
await db.collection('customers').insertOne({
122+
name: 'Alice Johnson',
123+
email: 'alice@example.com',
124+
addresses: [
125+
{
126+
type: 'home',
127+
street: '123 Maple St',
128+
city: 'Toronto',
129+
postalCode: 'M5V 2N4',
130+
country: 'Canada'
131+
},
132+
{
133+
type: 'work',
134+
street: '456 Bay St',
135+
city: 'Toronto',
136+
postalCode: 'M5H 2S1',
137+
country: 'Canada'
138+
}
139+
]
140+
});
141+
142+
// Act - Update a field in the first array element
143+
const updateSql = `UPDATE customers SET addresses[0].postalCode = 'T1K 4B8' WHERE name = 'Alice Johnson'`;
144+
145+
// Execute the SQL via QueryLeaf
146+
await queryLeaf.execute(updateSql);
147+
148+
// Get the result after the update
149+
const updatedCustomer = await db.collection('customers').findOne({ name: 'Alice Johnson' });
150+
151+
// Assert - the field in the array element should be updated
152+
expect(updatedCustomer?.addresses[0]?.postalCode).toBe('T1K 4B8');
153+
154+
// Make sure other fields in the array element remain intact
155+
expect(updatedCustomer?.addresses[0]?.type).toBe('home');
156+
expect(updatedCustomer?.addresses[0]?.street).toBe('123 Maple St');
157+
expect(updatedCustomer?.addresses[0]?.city).toBe('Toronto');
158+
159+
// Make sure the second array element is unchanged
160+
expect(updatedCustomer?.addresses[1]?.postalCode).toBe('M5H 2S1');
161+
162+
// Make sure no top-level fields were created by mistake
163+
expect(updatedCustomer).not.toHaveProperty('postalCode');
164+
});
165+
});

0 commit comments

Comments
 (0)