4
4
import apoc .load .util .LoadJdbcConfig ;
5
5
import apoc .result .RowResult ;
6
6
import org .apache .commons .codec .binary .Hex ;
7
+ import apoc .util .Util ;
7
8
import org .apache .commons .lang3 .StringUtils ;
8
9
import org .neo4j .graphdb .GraphDatabaseService ;
9
10
import org .neo4j .graphdb .Transaction ;
23
24
import static apoc .load .jdbc .Jdbc .executeQuery ;
24
25
import static apoc .load .jdbc .Jdbc .executeUpdate ;
25
26
import static apoc .load .util .JdbcUtil .*;
27
+ import static apoc .util .ExtendedUtil .batchIterator ;
26
28
27
29
@ Extended
28
30
public class Analytics {
31
+
29
32
public static final String PROVIDER_CONF_KEY = "provider" ;
30
33
public static final String TABLE_NAME_CONF_KEY = "tableName" ;
34
+ public static final String BATCH_SIZE_CONF_KEY = "batchSize" ;
35
+ public static final String WRITE_MODE_CONF_KEY = "writeMode" ;
36
+
37
+ public static final int BATCH_SIZE_DEFAULT = 10000 ;
31
38
public static final String TABLE_NAME_DEFAULT_CONF_KEY = "neo4j_tmp_table" ;
39
+
40
+ public static final String EMPTY_SQL_QUERY_ERROR = "The SQL query is empty" ;
41
+ public static final String EMPTY_NEO4J_QUERY_ERROR = "The Neo4j query is empty" ;
42
+ public static final String WRONG_BATCH_SIZE_ERR = "The batchSize value is invalid" ;
43
+
44
+ public enum WriteMode {
45
+ APPEND , CREATE
46
+ }
32
47
33
48
public enum Provider {
34
49
DUCKDB (DUCK_TYPE_MAP , "\" %s\" %s" ),
@@ -81,43 +96,56 @@ public Stream<RowResult> aggregate(
81
96
AtomicReference <String > createTable = new AtomicReference <>();
82
97
final Provider provider = Provider .valueOf ((String ) config .getOrDefault (PROVIDER_CONF_KEY , Provider .DUCKDB .name ()));
83
98
final String tableName = (String ) config .getOrDefault (TABLE_NAME_CONF_KEY , TABLE_NAME_DEFAULT_CONF_KEY );
99
+ final int batchSize = Util .toInteger (config .getOrDefault (BATCH_SIZE_CONF_KEY , BATCH_SIZE_DEFAULT ));
100
+ String writeModeString = (String ) config .getOrDefault (WRITE_MODE_CONF_KEY , WriteMode .CREATE .toString ());
101
+ WriteMode writeMode = WriteMode .valueOf (writeModeString .toUpperCase ());
84
102
85
103
AtomicReference <String > columns = new AtomicReference <>();
86
- AtomicReference <String > queryInsert = new AtomicReference <>();
87
-
88
- db .executeTransactionally (neo4jQuery ,
104
+ AtomicReference <List <String >> queriesInsert = new AtomicReference <>();
105
+
106
+ if (StringUtils .isBlank (neo4jQuery )) {
107
+ throw new RuntimeException (EMPTY_NEO4J_QUERY_ERROR );
108
+ }
109
+ if (StringUtils .isBlank (sqlQuery )) {
110
+ throw new RuntimeException (EMPTY_SQL_QUERY_ERROR );
111
+ }
112
+ if (batchSize < 1 ) {
113
+ throw new RuntimeException (WRONG_BATCH_SIZE_ERR );
114
+ }
115
+
116
+ boolean isCreate = writeMode .equals (WriteMode .CREATE );
117
+ db .executeTransactionally (neo4jQuery ,
89
118
Map .of (),
90
119
result -> {
91
- List <String > sqlValuesForQueryInsert = new ArrayList <>();
92
- result .forEachRemaining (map -> {
93
-
94
- if (createTable .get () == null ) {
120
+ List <String > insertClause = batchIterator (result , batchSize , map -> {
121
+ if (isCreate && createTable .get () == null ) {
95
122
String tempTableClause = getTempTableClause (map , provider , tableName );
96
123
createTable .set (tempTableClause );
97
124
}
98
125
99
- // convert Neo4j row result to SQL row
100
126
final String row = getStreamSortedByKey (map )
101
127
.map (Map .Entry ::getValue )
102
128
.map (i -> Analytics .formatSqlValue (i , provider ))
103
129
.collect (Collectors .joining ("," ));
104
-
105
- // add SQL row for query insert
106
- sqlValuesForQueryInsert . add ( "(" + row + ")" );
107
- } );
108
-
109
- // add values to `INSERT INTO ...` clause
110
- String sqlValues = StringUtils . join ( sqlValuesForQueryInsert , "," );
111
- String insertClause = String . format ( "INSERT INTO %s VALUES %s" ,
112
- tableName , sqlValues
113
- );
114
- queryInsert .set (insertClause );
115
-
130
+ return "(" + row + ")" ;
131
+ })
132
+ . map ( i -> {
133
+ String sqlValues = String . join ( "," , i );
134
+ return String . format ( "INSERT INTO %s VALUES %s" ,
135
+ tableName , sqlValues
136
+ );
137
+ })
138
+ . toList ();
139
+
140
+ queriesInsert .set (insertClause );
141
+
116
142
// columns to handle error msg
117
- String neo4jResultColumns = result .columns ().stream ()
118
- .sorted ()
119
- .collect (Collectors .joining ("," ));
120
- columns .set (neo4jResultColumns );
143
+ if (columns .get () == null ) {
144
+ String neo4jResultColumns = result .columns ().stream ()
145
+ .sorted ()
146
+ .collect (Collectors .joining ("," ));
147
+ columns .set (neo4jResultColumns );
148
+ }
121
149
return null ;
122
150
});
123
151
@@ -133,11 +161,16 @@ public Stream<RowResult> aggregate(
133
161
Object [] paramsArray = params .toArray (new Object [params .size ()]);
134
162
135
163
// Step 1. Create temporary table
136
- executeUpdate (urlOrKey , createTable .get (), config , connection , log , paramsArray );
164
+ if (isCreate ) {
165
+ executeUpdate (urlOrKey , createTable .get (), config , connection , log , paramsArray );
166
+ }
137
167
138
- // Step 2. Insert data in temp table
139
- executeUpdate (urlOrKey , queryInsert .get (), config , connection , log , paramsArray );
140
168
169
+ // Step 2. Insert data in temp table
170
+ queriesInsert .get ().forEach (
171
+ query -> executeUpdate (urlOrKey , query , config , connection , log , paramsArray )
172
+ );
173
+
141
174
try {
142
175
// Step 3. Return data from temp table
143
176
return executeQuery (urlOrKey , sqlQuery , config , connection , log , paramsArray );
0 commit comments