w-orm-postgresql 1.0.10 → 1.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -957,6 +957,32 @@ describe('insert nInserted', function() {
957
957
  let vdup = await wo.selectByPk('2025-02-01T00:05:00Z')
958
958
  vget[8] = vdup.name
959
959
 
960
+ //跨批次之插入
961
+ //欄位數愈多則單一語句可送之筆數愈少, 以21欄之資料表令每批上限為3120筆, 送3200筆即須分為2批
962
+ //其中既有主鍵者跳過而其餘照插, 與insertBulk之全有全無不同
963
+ let clBat = 'insertbatch'
964
+ let woBat = WOrm(genOpt(clBat))
965
+ let vSample = { time: '2000-01-01T00:00:00Z' }
966
+ Array.from({ length: 20 }, (v, k) => k).forEach((k) => {
967
+ vSample[`f${k}`] = 'abc'
968
+ })
969
+ await woBat.createTable(clBat, null, vSample).catch(() => {})
970
+ await woBat.delAll()
971
+
972
+ //genRec
973
+ let genRec = (k) => {
974
+ let v = { time: new Date(Date.UTC(2025, 8, 1, 0, 0, 0) + k * 1000).toISOString() }
975
+ Array.from({ length: 20 }, (v2, k2) => k2).forEach((k2) => {
976
+ v[`f${k2}`] = `v${k}-${k2}`
977
+ })
978
+ return v
979
+ }
980
+
981
+ //先插入末筆之主鍵, 令後續之大批次於第2批撞上該筆
982
+ await woBat.insert(genRec(3199))
983
+ vget[9] = await woBat.insert(Array.from({ length: 3200 }, (v, k) => genRec(k)))
984
+ vget[10] = (await woBat.select()).length
985
+
960
986
  })
961
987
 
962
988
  vans[1] = { n: 3, nInserted: 3, ok: 1 }
@@ -999,6 +1025,16 @@ describe('insert nInserted', function() {
999
1025
  assert.strict.deepStrictEqual(vget[8], vans[8])
1000
1026
  })
1001
1027
 
1028
+ vans[9] = { n: 3200, nInserted: 3199, ok: 1 }
1029
+ it(`should get ${JSON.stringify(vans[9])} for insert across batches with 1 pk existed`, async function() {
1030
+ assert.strict.deepStrictEqual(vget[9], vans[9])
1031
+ })
1032
+
1033
+ vans[10] = 3200
1034
+ it(`should get ${JSON.stringify(vans[10])} for records after insert across batches`, async function() {
1035
+ assert.strict.deepStrictEqual(vget[10], vans[10])
1036
+ })
1037
+
1002
1038
  })
1003
1039
 
1004
1040
 
@@ -1434,6 +1470,470 @@ describe('custom pk', function() {
1434
1470
  })
1435
1471
 
1436
1472
 
1473
+ describe('events', function() {
1474
+ let vans = {}
1475
+ let vget = {}
1476
+
1477
+ before(async function() {
1478
+ this.timeout(120000)
1479
+
1480
+ //wo
1481
+ let cl = 'events'
1482
+ let wo = WOrm(genOpt(cl))
1483
+
1484
+ //createTable
1485
+ await wo.createTable(cl, null, {
1486
+ time: '2000-01-01T00:00:00Z',
1487
+ name: 'abc',
1488
+ value: 0.1,
1489
+ }).catch(() => {})
1490
+ await wo.delAll()
1491
+
1492
+ //evChange, evError, 收集事件供斷言
1493
+ let evChange = []
1494
+ let evError = []
1495
+ wo.on('change', (mode, data, res) => {
1496
+ evChange.push({ mode, res })
1497
+ })
1498
+ wo.on('error', (mode, data, err) => {
1499
+ evError.push({ mode, err })
1500
+ })
1501
+
1502
+ //reset
1503
+ let reset = () => {
1504
+ evChange = []
1505
+ evError = []
1506
+ }
1507
+
1508
+ //t
1509
+ let t1 = '2025-07-01T00:00:00Z'
1510
+ let t2 = '2025-07-01T00:01:00Z'
1511
+ let t3 = '2025-07-01T00:02:00Z'
1512
+ let t4 = '2025-07-01T00:03:00Z'
1513
+ let tNone = '2025-07-01T23:00:00Z'
1514
+
1515
+ //insert正常, 發change且不發error
1516
+ reset()
1517
+ await wo.insert([
1518
+ { time: t1, name: 'peter', value: 1 },
1519
+ { time: t2, name: 'rosemary', value: 2 },
1520
+ ])
1521
+ vget[1] = evChange.map((v) => v.mode)
1522
+ vget[2] = evError.length
1523
+
1524
+ //insert全數已存在為正常結果, 不得發error
1525
+ reset()
1526
+ await wo.insert({ time: t1, name: 'peter', value: 1 })
1527
+ vget[3] = evError.length
1528
+
1529
+ //save之逐筆插入另發mode為insert之change, 整批再發save
1530
+ reset()
1531
+ await wo.save({ time: t3, name: 'sandler' })
1532
+ vget[4] = evChange.map((v) => v.mode)
1533
+
1534
+ //save合併後內容相同為正常結果, 不得發error
1535
+ reset()
1536
+ await wo.save({ time: t3, name: 'sandler' })
1537
+ vget[5] = evError.length
1538
+
1539
+ //save逐筆失敗發error且整批仍resolve
1540
+ reset()
1541
+ let rsf = await wo.save([
1542
+ { time: t1, name: 'ok' },
1543
+ { time: t4, notexistcol: 1 },
1544
+ ])
1545
+ vget[6] = evError.map((v) => v.mode)
1546
+ vget[7] = isestr(evError[0].err)
1547
+ vget[8] = rsf.length
1548
+ vget[9] = evChange.map((v) => v.mode)
1549
+
1550
+ //del未帶有效主鍵為逐筆失敗, 發error
1551
+ reset()
1552
+ await wo.del([{ name: 'no-pk' }])
1553
+ vget[10] = evError.map((v) => v.mode)
1554
+
1555
+ //del主鍵未命中為正常結果, 不得發error
1556
+ reset()
1557
+ await wo.del({ time: tNone })
1558
+ vget[11] = evError.length
1559
+
1560
+ //selectByPk查無數據為正常結果, 不得發error
1561
+ reset()
1562
+ await wo.selectByPk(tNone)
1563
+ vget[12] = evError.length
1564
+
1565
+ //selectByPk主鍵值無效為正常結果, 不得發error
1566
+ reset()
1567
+ await wo.selectByPk('abc')
1568
+ vget[13] = evError.length
1569
+
1570
+ //delAll條件無命中為正常結果, 不得發error
1571
+ reset()
1572
+ await wo.delAll({ name: 'notexist' })
1573
+ vget[14] = evError.length
1574
+
1575
+ //整批性錯誤於reject之前發出error
1576
+ reset()
1577
+ vget[15] = await wo.insert({ name: 'no-pk' })
1578
+ .then(() => 'resolved')
1579
+ .catch(() => 'rejected')
1580
+ vget[16] = evError.map((v) => v.mode)
1581
+
1582
+ //T10.1第2條, 操作行為不得因監聽者之有無而改變
1583
+ //woNo未註冊任何監聽, woOn註冊error監聽, 兩者之回傳值須完全相同
1584
+ let woNo = WOrm(genOpt(cl))
1585
+ let woOn = WOrm(genOpt(cl))
1586
+ woOn.on('error', () => {})
1587
+
1588
+ //整批性錯誤
1589
+ let rNo1 = await woNo.insert({ name: 'no-pk' })
1590
+ .then(() => 'resolved')
1591
+ .catch((err) => `rejected:${err}`)
1592
+ let rOn1 = await woOn.insert({ name: 'no-pk' })
1593
+ .then(() => 'resolved')
1594
+ .catch((err) => `rejected:${err}`)
1595
+ vget[17] = rNo1 === rOn1
1596
+ vget[18] = rNo1.indexOf('rejected:') === 0
1597
+
1598
+ //逐筆失敗
1599
+ let rNo2 = await woNo.del([{ name: 'no-pk' }])
1600
+ let rOn2 = await woOn.del([{ name: 'no-pk' }])
1601
+ vget[19] = JSON.stringify(rNo2) === JSON.stringify(rOn2)
1602
+
1603
+ //正常操作
1604
+ let rNo3 = await woNo.selectByPk(t1)
1605
+ let rOn3 = await woOn.selectByPk(t1)
1606
+ vget[20] = JSON.stringify(rNo3) === JSON.stringify(rOn3)
1607
+
1608
+ })
1609
+
1610
+ vans[1] = ['insert']
1611
+ it(`should get ${JSON.stringify(vans[1])} for change modes by insert`, async function() {
1612
+ assert.strict.deepStrictEqual(vget[1], vans[1])
1613
+ })
1614
+
1615
+ vans[2] = 0
1616
+ it(`should get ${JSON.stringify(vans[2])} for error count by insert`, async function() {
1617
+ assert.strict.deepStrictEqual(vget[2], vans[2])
1618
+ })
1619
+
1620
+ vans[3] = 0
1621
+ it(`should get ${JSON.stringify(vans[3])} for error count by insert with all existed`, async function() {
1622
+ assert.strict.deepStrictEqual(vget[3], vans[3])
1623
+ })
1624
+
1625
+ vans[4] = ['insert', 'save']
1626
+ it(`should get ${JSON.stringify(vans[4])} for change modes by save with autoInsert`, async function() {
1627
+ assert.strict.deepStrictEqual(vget[4], vans[4])
1628
+ })
1629
+
1630
+ vans[5] = 0
1631
+ it(`should get ${JSON.stringify(vans[5])} for error count by save with same content`, async function() {
1632
+ assert.strict.deepStrictEqual(vget[5], vans[5])
1633
+ })
1634
+
1635
+ vans[6] = ['save']
1636
+ it(`should get ${JSON.stringify(vans[6])} for error modes by save with 1 failed record`, async function() {
1637
+ assert.strict.deepStrictEqual(vget[6], vans[6])
1638
+ })
1639
+
1640
+ vans[7] = true
1641
+ it(`should get ${JSON.stringify(vans[7])} for err of error event being string`, async function() {
1642
+ assert.strict.deepStrictEqual(vget[7], vans[7])
1643
+ })
1644
+
1645
+ vans[8] = 2
1646
+ it(`should get ${JSON.stringify(vans[8])} for length of save result while 1 record failed`, async function() {
1647
+ assert.strict.deepStrictEqual(vget[8], vans[8])
1648
+ })
1649
+
1650
+ vans[9] = ['save']
1651
+ it(`should get ${JSON.stringify(vans[9])} for change modes by save while 1 record failed`, async function() {
1652
+ assert.strict.deepStrictEqual(vget[9], vans[9])
1653
+ })
1654
+
1655
+ vans[10] = ['del']
1656
+ it(`should get ${JSON.stringify(vans[10])} for error modes by del without valid pk`, async function() {
1657
+ assert.strict.deepStrictEqual(vget[10], vans[10])
1658
+ })
1659
+
1660
+ vans[11] = 0
1661
+ it(`should get ${JSON.stringify(vans[11])} for error count by del with pk not existed`, async function() {
1662
+ assert.strict.deepStrictEqual(vget[11], vans[11])
1663
+ })
1664
+
1665
+ vans[12] = 0
1666
+ it(`should get ${JSON.stringify(vans[12])} for error count by selectByPk with pk not existed`, async function() {
1667
+ assert.strict.deepStrictEqual(vget[12], vans[12])
1668
+ })
1669
+
1670
+ vans[13] = 0
1671
+ it(`should get ${JSON.stringify(vans[13])} for error count by selectByPk with pk invalid`, async function() {
1672
+ assert.strict.deepStrictEqual(vget[13], vans[13])
1673
+ })
1674
+
1675
+ vans[14] = 0
1676
+ it(`should get ${JSON.stringify(vans[14])} for error count by delAll with find matched nothing`, async function() {
1677
+ assert.strict.deepStrictEqual(vget[14], vans[14])
1678
+ })
1679
+
1680
+ vans[15] = 'rejected'
1681
+ it(`should get ${JSON.stringify(vans[15])} for insert without valid pk`, async function() {
1682
+ assert.strict.deepStrictEqual(vget[15], vans[15])
1683
+ })
1684
+
1685
+ vans[16] = ['insert']
1686
+ it(`should get ${JSON.stringify(vans[16])} for error modes by insert without valid pk`, async function() {
1687
+ assert.strict.deepStrictEqual(vget[16], vans[16])
1688
+ })
1689
+
1690
+ vans[17] = true
1691
+ it(`should get ${JSON.stringify(vans[17])} for same reject value with and without error listener`, async function() {
1692
+ assert.strict.deepStrictEqual(vget[17], vans[17])
1693
+ })
1694
+
1695
+ vans[18] = true
1696
+ it(`should get ${JSON.stringify(vans[18])} for still rejecting without error listener`, async function() {
1697
+ assert.strict.deepStrictEqual(vget[18], vans[18])
1698
+ })
1699
+
1700
+ vans[19] = true
1701
+ it(`should get ${JSON.stringify(vans[19])} for same del result with and without error listener`, async function() {
1702
+ assert.strict.deepStrictEqual(vget[19], vans[19])
1703
+ })
1704
+
1705
+ vans[20] = true
1706
+ it(`should get ${JSON.stringify(vans[20])} for same selectByPk result with and without error listener`, async function() {
1707
+ assert.strict.deepStrictEqual(vget[20], vans[20])
1708
+ })
1709
+
1710
+ })
1711
+
1712
+
1713
+ describe('insertBulk', function() {
1714
+ let vans = {}
1715
+ let vget = {}
1716
+
1717
+ before(async function() {
1718
+ this.timeout(300000)
1719
+
1720
+ //wo
1721
+ let cl = 'bulk'
1722
+ let wo = WOrm(genOpt(cl))
1723
+
1724
+ //createTable
1725
+ await wo.createTable(cl, null, {
1726
+ time: '2000-01-01T00:00:00Z',
1727
+ name: 'abc',
1728
+ value: 0.1,
1729
+ }).catch(() => {})
1730
+ await wo.delAll()
1731
+
1732
+ //genTime, 依序產生不重複之主鍵值
1733
+ let genTime = (k) => {
1734
+ return new Date(Date.UTC(2025, 7, 1, 0, 0, 0) + k * 1000).toISOString()
1735
+ }
1736
+
1737
+ //evChange, evError
1738
+ let evChange = []
1739
+ let evError = []
1740
+ wo.on('change', (mode) => {
1741
+ evChange.push(mode)
1742
+ })
1743
+ wo.on('error', (mode) => {
1744
+ evError.push(mode)
1745
+ })
1746
+
1747
+ //nRec, 取得目前筆數
1748
+ let nRec = async () => {
1749
+ return (await wo.select()).length
1750
+ }
1751
+
1752
+ //無衝突, nInserted須等於n
1753
+ vget[1] = await wo.insertBulk([
1754
+ { time: genTime(0), name: 'peter', value: 1 },
1755
+ { time: genTime(1), name: 'rosemary', value: 2 },
1756
+ { time: genTime(2), name: 'kettle', value: 3 },
1757
+ ])
1758
+ vget[2] = await nRec()
1759
+ vget[3] = evChange.slice()
1760
+
1761
+ //撞既有主鍵, 整批reject且不寫入任何一筆
1762
+ evChange = []
1763
+ evError = []
1764
+ vget[4] = await wo.insertBulk([
1765
+ { time: genTime(10), name: 'new-1', value: 11 },
1766
+ { time: genTime(0), name: 'conflict', value: 12 },
1767
+ { time: genTime(11), name: 'new-2', value: 13 },
1768
+ ])
1769
+ .then(() => 'resolved')
1770
+ .catch(() => 'rejected')
1771
+ vget[5] = await nRec()
1772
+ vget[6] = evError.slice()
1773
+ vget[7] = evChange.slice()
1774
+
1775
+ //同批含重複主鍵, 亦視為衝突而整批reject
1776
+ evChange = []
1777
+ evError = []
1778
+ vget[8] = await wo.insertBulk([
1779
+ { time: genTime(20), name: 'dup-1', value: 21 },
1780
+ { time: genTime(20), name: 'dup-2', value: 22 },
1781
+ { time: genTime(21), name: 'uniq', value: 23 },
1782
+ ])
1783
+ .then(() => 'resolved')
1784
+ .catch(() => 'rejected')
1785
+ vget[9] = await nRec()
1786
+
1787
+ //未帶有效主鍵, 不補值而整批reject
1788
+ vget[10] = await wo.insertBulk([
1789
+ { time: genTime(30), name: 'ok', value: 31 },
1790
+ { name: 'no-pk', value: 32 },
1791
+ ])
1792
+ .then(() => 'resolved')
1793
+ .catch(() => 'rejected')
1794
+ vget[11] = await nRec()
1795
+
1796
+ //輸入無效
1797
+ vget[12] = await wo.insertBulk(null)
1798
+
1799
+ //單一物件亦回傳單一物件
1800
+ vget[13] = await wo.insertBulk({ time: genTime(40), name: 'single', value: 41 })
1801
+ vget[14] = await nRec()
1802
+
1803
+ //跨批次之回滾
1804
+ //欄位數愈多則單一語句可送之筆數愈少, 以21欄之資料表令每批上限為3120筆,
1805
+ //送3200筆即須分為2批, 末筆撞既有主鍵時第1批已送出, 須由交易回滾方能維持全有全無
1806
+ let clBat = 'bulkbatch'
1807
+ let woBat = WOrm(genOpt(clBat))
1808
+ let vSample = { time: '2000-01-01T00:00:00Z' }
1809
+ Array.from({ length: 20 }, (v, k) => k).forEach((k) => {
1810
+ vSample[`f${k}`] = 'abc'
1811
+ })
1812
+ await woBat.createTable(clBat, null, vSample).catch(() => {})
1813
+ await woBat.delAll()
1814
+
1815
+ //genRec
1816
+ let genRec = (k) => {
1817
+ let v = { time: genTime(k) }
1818
+ Array.from({ length: 20 }, (v2, k2) => k2).forEach((k2) => {
1819
+ v[`f${k2}`] = `v${k}-${k2}`
1820
+ })
1821
+ return v
1822
+ }
1823
+
1824
+ //先插入末筆之主鍵, 令後續之大批次於第2批撞上
1825
+ let kLast = 3199
1826
+ await woBat.insert(genRec(kLast))
1827
+ let nBefore = (await woBat.select()).length
1828
+
1829
+ //3200筆之批次, 末筆與既有衝突
1830
+ vget[15] = await woBat.insertBulk(Array.from({ length: 3200 }, (v, k) => genRec(k)))
1831
+ .then(() => 'resolved')
1832
+ .catch(() => 'rejected')
1833
+
1834
+ //失敗後資料表不得有任何新增
1835
+ vget[16] = (await woBat.select()).length - nBefore
1836
+
1837
+ //跨批次且無衝突者須全數寫入
1838
+ await woBat.delAll()
1839
+ vget[17] = await woBat.insertBulk(Array.from({ length: 3200 }, (v, k) => genRec(k)))
1840
+ vget[18] = (await woBat.select()).length
1841
+
1842
+ })
1843
+
1844
+ vans[1] = { n: 3, nInserted: 3, ok: 1 }
1845
+ it(`should get ${JSON.stringify(vans[1])} for insertBulk without conflict`, async function() {
1846
+ assert.strict.deepStrictEqual(vget[1], vans[1])
1847
+ })
1848
+
1849
+ vans[2] = 3
1850
+ it(`should get ${JSON.stringify(vans[2])} for records after insertBulk without conflict`, async function() {
1851
+ assert.strict.deepStrictEqual(vget[2], vans[2])
1852
+ })
1853
+
1854
+ vans[3] = ['insertBulk']
1855
+ it(`should get ${JSON.stringify(vans[3])} for change modes by insertBulk`, async function() {
1856
+ assert.strict.deepStrictEqual(vget[3], vans[3])
1857
+ })
1858
+
1859
+ vans[4] = 'rejected'
1860
+ it(`should get ${JSON.stringify(vans[4])} for insertBulk with pk existed`, async function() {
1861
+ assert.strict.deepStrictEqual(vget[4], vans[4])
1862
+ })
1863
+
1864
+ vans[5] = 3
1865
+ it(`should get ${JSON.stringify(vans[5])} for records after insertBulk with pk existed`, async function() {
1866
+ assert.strict.deepStrictEqual(vget[5], vans[5])
1867
+ })
1868
+
1869
+ vans[6] = ['insertBulk']
1870
+ it(`should get ${JSON.stringify(vans[6])} for error modes by insertBulk with pk existed`, async function() {
1871
+ assert.strict.deepStrictEqual(vget[6], vans[6])
1872
+ })
1873
+
1874
+ vans[7] = []
1875
+ it(`should get ${JSON.stringify(vans[7])} for change modes by insertBulk rejected`, async function() {
1876
+ assert.strict.deepStrictEqual(vget[7], vans[7])
1877
+ })
1878
+
1879
+ vans[8] = 'rejected'
1880
+ it(`should get ${JSON.stringify(vans[8])} for insertBulk with duplicated pk in same batch`, async function() {
1881
+ assert.strict.deepStrictEqual(vget[8], vans[8])
1882
+ })
1883
+
1884
+ vans[9] = 3
1885
+ it(`should get ${JSON.stringify(vans[9])} for records after insertBulk with duplicated pk`, async function() {
1886
+ assert.strict.deepStrictEqual(vget[9], vans[9])
1887
+ })
1888
+
1889
+ vans[10] = 'rejected'
1890
+ it(`should get ${JSON.stringify(vans[10])} for insertBulk without valid pk`, async function() {
1891
+ assert.strict.deepStrictEqual(vget[10], vans[10])
1892
+ })
1893
+
1894
+ vans[11] = 3
1895
+ it(`should get ${JSON.stringify(vans[11])} for records after insertBulk without valid pk`, async function() {
1896
+ assert.strict.deepStrictEqual(vget[11], vans[11])
1897
+ })
1898
+
1899
+ vans[12] = { n: 0, nInserted: 0, ok: 1 }
1900
+ it(`should get ${JSON.stringify(vans[12])} for insertBulk with invalid data`, async function() {
1901
+ assert.strict.deepStrictEqual(vget[12], vans[12])
1902
+ })
1903
+
1904
+ vans[13] = { n: 1, nInserted: 1, ok: 1 }
1905
+ it(`should get ${JSON.stringify(vans[13])} for insertBulk with single object`, async function() {
1906
+ assert.strict.deepStrictEqual(vget[13], vans[13])
1907
+ })
1908
+
1909
+ vans[14] = 4
1910
+ it(`should get ${JSON.stringify(vans[14])} for records after insertBulk with single object`, async function() {
1911
+ assert.strict.deepStrictEqual(vget[14], vans[14])
1912
+ })
1913
+
1914
+ vans[15] = 'rejected'
1915
+ it(`should get ${JSON.stringify(vans[15])} for insertBulk across batches with pk existed`, async function() {
1916
+ assert.strict.deepStrictEqual(vget[15], vans[15])
1917
+ })
1918
+
1919
+ vans[16] = 0
1920
+ it(`should get ${JSON.stringify(vans[16])} for increment of records after insertBulk across batches failed`, async function() {
1921
+ assert.strict.deepStrictEqual(vget[16], vans[16])
1922
+ })
1923
+
1924
+ vans[17] = { n: 3200, nInserted: 3200, ok: 1 }
1925
+ it(`should get ${JSON.stringify(vans[17])} for insertBulk across batches without conflict`, async function() {
1926
+ assert.strict.deepStrictEqual(vget[17], vans[17])
1927
+ })
1928
+
1929
+ vans[18] = 3200
1930
+ it(`should get ${JSON.stringify(vans[18])} for records after insertBulk across batches`, async function() {
1931
+ assert.strict.deepStrictEqual(vget[18], vans[18])
1932
+ })
1933
+
1934
+ })
1935
+
1936
+
1437
1937
  //runProc, 另起行程對同一資料表操作, 用於驗證跨行程之原子性
1438
1938
  //子行程以--input-type=module執行, 並以pathToFileURL轉換src路徑, 避免專案路徑含非ASCII字元而無法import
1439
1939
  function runProc(tag, opt, mode, payload) {