@@ -153,6 +153,13 @@ class BaseBuilder
153
153
*/
154
154
protected $ QBIgnore = false ;
155
155
156
+ /**
157
+ * QB Options data
158
+ *
159
+ * @var array
160
+ */
161
+ protected $ QBOptions ;
162
+
156
163
/**
157
164
* A reference to the database connection.
158
165
*
@@ -1719,6 +1726,238 @@ public function getWhere($where = null, ?int $limit = null, ?int $offset = 0, bo
1719
1726
return $ result ;
1720
1727
}
1721
1728
1729
+ /**
1730
+ * Compiles batch insert/upsert strings and runs the queries
1731
+ *
1732
+ * @throws DatabaseException
1733
+ *
1734
+ * @return false|int|string[] Number of rows inserted or FALSE on failure, SQL array when testMode
1735
+ */
1736
+ public function batchExecute (string $ renderMethod , ?array $ set = null , ?bool $ escape = null , int $ batchSize = 100 )
1737
+ {
1738
+ if ($ set === null ) {
1739
+ if (empty ($ this ->QBSet )) {
1740
+ if ($ this ->db ->DBDebug ) {
1741
+ throw new DatabaseException ('You must use the "set" method to update an entry. ' );
1742
+ }
1743
+
1744
+ return false ; // @codeCoverageIgnore
1745
+ }
1746
+ } elseif (empty ($ set )) {
1747
+ if ($ this ->db ->DBDebug ) {
1748
+ throw new DatabaseException ('insertBatch() called with no data ' );
1749
+ }
1750
+
1751
+ return false ; // @codeCoverageIgnore
1752
+ }
1753
+
1754
+ $ hasQBSet = $ set === null ;
1755
+
1756
+ $ table = $ this ->QBFrom [0 ];
1757
+
1758
+ $ affectedRows = 0 ;
1759
+ $ savedSQL = [];
1760
+
1761
+ if ($ hasQBSet ) {
1762
+ $ set = $ this ->QBSet ;
1763
+ }
1764
+
1765
+ for ($ i = 0 , $ total = count ($ set ); $ i < $ total ; $ i += $ batchSize ) {
1766
+ if ($ hasQBSet ) {
1767
+ $ QBSet = array_slice ($ this ->QBSet , $ i , $ batchSize );
1768
+ } else {
1769
+ $ this ->setBatch (array_slice ($ set , $ i , $ batchSize ), '' , $ escape );
1770
+ $ QBSet = $ this ->QBSet ;
1771
+ }
1772
+ $ sql = $ this ->{$ renderMethod }($ this ->db ->protectIdentifiers ($ table , true , null , false ), $ this ->QBKeys , $ QBSet );
1773
+
1774
+ if ($ this ->testMode ) {
1775
+ $ savedSQL [] = $ sql ;
1776
+ } else {
1777
+ $ this ->db ->query ($ sql , null , false );
1778
+ $ affectedRows += $ this ->db ->affectedRows ();
1779
+ }
1780
+
1781
+ if (! $ hasQBSet ) {
1782
+ $ this ->resetRun ([
1783
+ 'QBSet ' => [],
1784
+ 'QBKeys ' => [],
1785
+ ]);
1786
+ }
1787
+ }
1788
+
1789
+ $ this ->resetWrite ();
1790
+
1791
+ return $ this ->testMode ? $ savedSQL : $ affectedRows ;
1792
+ }
1793
+
1794
+ /**
1795
+ * Allows key/value pairs to be set for batch inserts/upserts
1796
+ *
1797
+ * @param mixed $key
1798
+ *
1799
+ * @return $this|null
1800
+ */
1801
+ public function setBatch ($ key , string $ value = '' , ?bool $ escape = null )
1802
+ {
1803
+ $ key = $ this ->batchObjectToArray ($ key );
1804
+
1805
+ if (! is_array ($ key )) {
1806
+ $ key = [$ key => $ value ];
1807
+ }
1808
+
1809
+ $ escape = is_bool ($ escape ) ? $ escape : $ this ->db ->protectIdentifiers ;
1810
+
1811
+ $ keys = array_keys ($ this ->objectToArray (current ($ key )));
1812
+ sort ($ keys );
1813
+
1814
+ foreach ($ key as $ row ) {
1815
+ $ row = $ this ->objectToArray ($ row );
1816
+ if (array_diff ($ keys , array_keys ($ row )) !== [] || array_diff (array_keys ($ row ), $ keys ) !== []) {
1817
+ // batch function above returns an error on an empty array
1818
+ $ this ->QBSet [] = [];
1819
+
1820
+ return null ;
1821
+ }
1822
+
1823
+ ksort ($ row ); // puts $row in the same order as our keys
1824
+
1825
+ $ clean = [];
1826
+
1827
+ foreach ($ row as $ rowValue ) {
1828
+ $ clean [] = $ escape ? $ this ->db ->escape ($ rowValue ) : $ rowValue ;
1829
+ }
1830
+
1831
+ $ row = $ clean ;
1832
+
1833
+ $ this ->QBSet [] = $ row ;
1834
+ }
1835
+
1836
+ foreach ($ keys as $ k ) {
1837
+ $ this ->QBKeys [] = $ this ->db ->protectIdentifiers ($ k , false );
1838
+ }
1839
+
1840
+ return $ this ;
1841
+ }
1842
+
1843
+ /**
1844
+ * Compiles an upsert query and returns the sql
1845
+ *
1846
+ * @throws DatabaseException
1847
+ *
1848
+ * @return bool|string
1849
+ */
1850
+ public function getCompiledUpsert (bool $ reset = true )
1851
+ {
1852
+ $ currentTestMode = $ this ->testMode ;
1853
+
1854
+ $ this ->testMode = true ;
1855
+
1856
+ $ sql = implode ("; \n" , $ this ->upsert ());
1857
+
1858
+ $ this ->testMode = $ currentTestMode ;
1859
+
1860
+ // this doesn't work with current implimentation - is cleared in upsert method
1861
+ if ($ reset === true ) {
1862
+ $ this ->resetWrite ();
1863
+ }
1864
+
1865
+ return $ this ->compileFinalQuery ($ sql );
1866
+ }
1867
+
1868
+ /**
1869
+ * Converts call to batchUpsert
1870
+ *
1871
+ * @param array|object|null $set
1872
+ *
1873
+ * @throws DatabaseException
1874
+ *
1875
+ * @return false|int|string[] Number of rows replaced or FALSE on failure, SQL array when testMode
1876
+ */
1877
+ public function upsert ($ set = null , ?bool $ escape = null )
1878
+ {
1879
+ if ($ set === null ) {
1880
+ $ set = empty ($ this ->binds ) ? null : [array_map (static fn ($ columnName ) => $ columnName [0 ], $ this ->binds )];
1881
+
1882
+ $ this ->binds = [];
1883
+
1884
+ $ this ->resetRun ([
1885
+ 'QBSet ' => [],
1886
+ 'QBKeys ' => [],
1887
+ ]);
1888
+ } else {
1889
+ $ set = [$ set ];
1890
+ }
1891
+
1892
+ return $ this ->batchExecute ('_upsertBatch ' , $ set , $ escape , 1 );
1893
+ }
1894
+
1895
+ /**
1896
+ * Compiles batch upsert strings and runs the queries
1897
+ *
1898
+ * @throws DatabaseException
1899
+ *
1900
+ * @return false|int|string[] Number of rows replaced or FALSE on failure, SQL array when testMode
1901
+ */
1902
+ public function upsertBatch (?array $ set = null , ?bool $ escape = null , int $ batchSize = 100 )
1903
+ {
1904
+ return $ this ->batchExecute ('_upsertBatch ' , $ set , $ escape , $ batchSize );
1905
+ }
1906
+
1907
+ /**
1908
+ * Generates a platform-specific upsertBatch string from the supplied data
1909
+ */
1910
+ protected function _upsertBatch (string $ table , array $ keys , array $ values ): string
1911
+ {
1912
+ $ updateFields = $ this ->QBOptions ['updateFields ' ] ?? $ keys ;
1913
+
1914
+ return 'INSERT INTO ' . $ table . ' ( ' . implode (', ' , $ keys ) . ') VALUES ' . implode (', ' , $ this ->getValues ($ values )) . ' ON DUPLICATE KEY UPDATE ' . implode (', ' , array_map (static fn ($ columnName ) => '` ' . trim ($ columnName , '` ' ) . '` = VALUES(` ' . trim ($ columnName , '` ' ) . '`) ' , $ updateFields ));
1915
+ }
1916
+
1917
+ /**
1918
+ * Sets constraints for upsert
1919
+ *
1920
+ * @param mixed $keys
1921
+ *
1922
+ * @return $this
1923
+ */
1924
+ public function onConstraint ($ keys )
1925
+ {
1926
+ if (! is_array ($ keys )) {
1927
+ $ keys = explode (', ' , $ keys );
1928
+ }
1929
+
1930
+ $ this ->QBOptions ['constraints ' ] = array_map (static fn ($ key ) => trim ($ key ), $ keys );
1931
+
1932
+ return $ this ;
1933
+ }
1934
+
1935
+ /**
1936
+ * Sets update fields for upsert
1937
+ *
1938
+ * @param mixed $keys
1939
+ *
1940
+ * @return $this
1941
+ */
1942
+ public function updateFields ($ keys )
1943
+ {
1944
+ if (! is_array ($ keys )) {
1945
+ $ keys = explode (', ' , $ keys );
1946
+ }
1947
+
1948
+ $ this ->QBOptions ['updateFields ' ] = array_map (static fn ($ key ) => trim ($ key ), $ keys );
1949
+
1950
+ return $ this ;
1951
+ }
1952
+
1953
+ /**
1954
+ * Converts value array of array to array of strings
1955
+ */
1956
+ protected function getValues (array $ values ): array
1957
+ {
1958
+ return array_map (static fn ($ index ) => '( ' . implode (', ' , $ index ) . ') ' , $ values );
1959
+ }
1960
+
1722
1961
/**
1723
1962
* Compiles batch insert strings and runs the queries
1724
1963
*
@@ -2828,6 +3067,7 @@ protected function resetWrite()
2828
3067
'QBKeys ' => [],
2829
3068
'QBLimit ' => false ,
2830
3069
'QBIgnore ' => false ,
3070
+ 'QBOptions ' => [],
2831
3071
]);
2832
3072
}
2833
3073
0 commit comments