embulk-output-oracle 0.6.3 → 0.6.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (27) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +43 -0
  3. data/build.gradle +2 -2
  4. data/classpath/{embulk-output-jdbc-0.6.3.jar → embulk-output-jdbc-0.6.4.jar} +0 -0
  5. data/classpath/embulk-output-oracle-0.6.4.jar +0 -0
  6. data/lib/embulk/native/x86_64-linux/libembulk-output-oracle-oci.so +0 -0
  7. data/lib/embulk/native/x86_64-windows/embulk-output-oracle-oci.dll +0 -0
  8. data/src/main/cpp/common/embulk-output-oracle-oci.cpp +27 -0
  9. data/src/main/cpp/linux/build.sh +15 -0
  10. data/src/main/cpp/windows/build.bat +26 -0
  11. data/src/main/cpp/windows/dllmain.cpp +25 -0
  12. data/src/main/cpp/windows/embulk-output-oracle-oci.sln +20 -0
  13. data/src/main/cpp/windows/embulk-output-oracle-oci.vcxproj +171 -0
  14. data/src/main/java/org/embulk/output/oracle/DirectBatchInsert.java +5 -39
  15. data/src/main/java/org/embulk/output/oracle/oci/BulkOCI.java +15 -0
  16. data/src/main/java/org/embulk/output/oracle/oci/OCI.java +1 -0
  17. data/src/main/java/org/embulk/output/oracle/oci/OCIManager.java +3 -2
  18. data/src/main/java/org/embulk/output/oracle/oci/OCIWrapper.java +103 -37
  19. data/src/main/java/org/embulk/output/oracle/oci/PrimitiveBulkOCI.java +47 -0
  20. data/src/main/java/org/embulk/output/oracle/oci/RowBuffer.java +50 -46
  21. data/src/main/java/org/embulk/output/oracle/oci/TableDefinition.java +9 -0
  22. data/src/test/java/org/embulk/output/oracle/OracleOutputPluginTest.java +622 -133
  23. data/src/test/resources/oracle/data/test3/test3.csv +9999 -0
  24. data/src/test/resources/oracle/yml/test-insert-direct-oci-method-large.yml +29 -0
  25. metadata +19 -8
  26. data/classpath/embulk-output-oracle-0.6.3.jar +0 -0
  27. data/src/test/java/org/embulk/output/oracle/OracleOutputPluginTestImpl.java +0 -620
@@ -0,0 +1,15 @@
1
+ package org.embulk.output.oracle.oci;
2
+
3
+ import jnr.ffi.Pointer;
4
+ import jnr.ffi.types.u_int16_t;
5
+ import jnr.ffi.types.u_int32_t;
6
+
7
+ public interface BulkOCI {
8
+
9
+ short embulk_output_oracle_OCIDirPathColArrayEntriesSet(Pointer dpca,
10
+ Pointer errhp,
11
+ @u_int16_t short columnCount,
12
+ @u_int32_t int rowCount,
13
+ Pointer data,
14
+ Pointer sizes);
15
+ }
@@ -32,6 +32,7 @@ public interface OCI
32
32
  static int OCI_ATTR_SCHEMA_NAME = 9;
33
33
  static int OCI_ATTR_CHARSET_ID = 31;
34
34
  static int OCI_ATTR_DATEFORMAT = 75;
35
+ static int OCI_ATTR_BUF_SIZE = 77;
35
36
  static int OCI_ATTR_NUM_ROWS = 81;
36
37
  static int OCI_ATTR_NUM_COLS = 102;
37
38
  static int OCI_ATTR_LIST_COLUMNS = 103;
@@ -20,7 +20,8 @@ public class OCIManager
20
20
  private Map<Object, OCIWrapperAndCounter> ociAndCounters = new HashMap<Object, OCIWrapperAndCounter>();
21
21
 
22
22
 
23
- public OCIWrapper open(Object key, String dbName, String userName, String password, TableDefinition tableDefinition) throws SQLException
23
+ public OCIWrapper open(Object key, String dbName, String userName, String password, TableDefinition tableDefinition, int bufferSize)
24
+ throws SQLException
24
25
  {
25
26
  synchronized(ociAndCounters) {
26
27
  OCIWrapperAndCounter ociAndCounter;
@@ -31,7 +32,7 @@ public class OCIManager
31
32
  ociAndCounter = new OCIWrapperAndCounter();
32
33
  ociAndCounter.oci = new OCIWrapper();
33
34
  ociAndCounter.oci.open(dbName, userName, password);
34
- ociAndCounter.oci.prepareLoad(tableDefinition);
35
+ ociAndCounter.oci.prepareLoad(tableDefinition, bufferSize);
35
36
  ociAndCounters.put(key, ociAndCounter);
36
37
  }
37
38
  ociAndCounter.counter++;
@@ -1,13 +1,17 @@
1
1
  package org.embulk.output.oracle.oci;
2
2
 
3
+ import java.io.File;
4
+ import java.net.MalformedURLException;
5
+ import java.net.URISyntaxException;
6
+ import java.net.URL;
3
7
  import java.nio.ByteBuffer;
4
8
  import java.nio.charset.Charset;
5
9
  import java.sql.SQLException;
6
10
 
7
11
  import jnr.ffi.LibraryLoader;
12
+ import jnr.ffi.Platform;
8
13
  import jnr.ffi.Pointer;
9
14
  import jnr.ffi.Runtime;
10
- import jnr.ffi.provider.BoundedMemoryIO;
11
15
  import jnr.ffi.provider.jffi.ArrayMemoryIO;
12
16
  import jnr.ffi.provider.jffi.ByteBufferMemoryIO;
13
17
 
@@ -17,7 +21,10 @@ import org.slf4j.Logger;
17
21
 
18
22
  public class OCIWrapper
19
23
  {
24
+ private static final String PLUGIN_NAME = "embulk-output-oracle";
25
+
20
26
  private static OCI oci;
27
+ private static BulkOCI bulkOci;
21
28
 
22
29
  private final Logger logger = Exec.getLogger(getClass());
23
30
 
@@ -31,6 +38,8 @@ public class OCIWrapper
31
38
 
32
39
  private TableDefinition tableDefinition;
33
40
  private int maxRowCount;
41
+ private long totalRows;
42
+ private int loadCount;
34
43
 
35
44
  private boolean errorOccured;
36
45
  private boolean committedOrRollbacked;
@@ -43,14 +52,17 @@ public class OCIWrapper
43
52
 
44
53
  synchronized (OCIWrapper.class) {
45
54
  if (oci == null) {
46
- oci = loadLibrary();
55
+ oci = loadOCILibrary();
56
+ }
57
+ if (bulkOci == null) {
58
+ bulkOci = loadBulkOCILibrary(oci);
47
59
  }
48
60
  }
49
61
  }
50
62
 
51
- private OCI loadLibrary()
63
+ private OCI loadOCILibrary()
52
64
  {
53
- logger.info("Loading OCI library.");
65
+ logger.info("OCI : Loading OCI library.");
54
66
 
55
67
  // "oci" for Windows, "clntsh" for Linux
56
68
  StringBuilder libraryNames = new StringBuilder();
@@ -69,12 +81,57 @@ public class OCIWrapper
69
81
  throw new UnsatisfiedLinkError("Cannot find library: " + libraryNames);
70
82
  }
71
83
 
84
+ private BulkOCI loadBulkOCILibrary(OCI oci)
85
+ {
86
+ String libraryName = "embulk-output-oracle-oci";
87
+ logger.info("OCI : Loading " + libraryName + " library.");
88
+
89
+ Platform platform = Platform.getNativePlatform();
90
+
91
+ File folder = getPluginRoot();
92
+ folder = new File(new File(new File(new File(folder ,"lib"), "embulk"), "native"), platform.getName());
93
+
94
+ File file = new File(folder, System.mapLibraryName(libraryName));
95
+ if (!file.exists()) {
96
+ logger.info("OCI : Library '" + file.getAbsolutePath() + "' doesn't exist, so Java implementation is used instead.");
97
+ return new PrimitiveBulkOCI(oci);
98
+ }
99
+
100
+ logger.info("OCI : Library '" + file.getAbsolutePath() + "' is found.");
101
+ return LibraryLoader.create(BulkOCI.class).search(folder.getAbsolutePath()).failImmediately().load(libraryName);
102
+ }
103
+
104
+ private File getPluginRoot()
105
+ {
106
+ try {
107
+ URL url = getClass().getResource("/" + getClass().getName().replace('.', '/') + ".class");
108
+ if (url.toString().startsWith("jar:")) {
109
+ url = new URL(url.toString().replaceAll("^jar:", "").replaceAll("![^!]*$", ""));
110
+ }
111
+
112
+ File folder = new File(url.toURI()).getParentFile();
113
+ for (;; folder = folder.getParentFile()) {
114
+ if (folder == null) {
115
+ String message = String.format("OCI : %s folder not found.", PLUGIN_NAME);
116
+ throw new RuntimeException(message);
117
+ }
118
+
119
+ if (folder.getName().startsWith(PLUGIN_NAME)) {
120
+ return folder;
121
+ }
122
+ }
123
+ } catch (MalformedURLException | URISyntaxException e) {
124
+ throw new RuntimeException(e);
125
+ }
126
+ }
127
+
72
128
  public void open(String dbName, String userName, String password) throws SQLException
73
129
  {
74
130
  Pointer envHandlePointer = createPointerPointer();
131
+ // OCI_THREADED is not needed because synchronized in Java side.
75
132
  check("OCIEnvCreate", oci.OCIEnvCreate(
76
133
  envHandlePointer,
77
- OCI.OCI_THREADED | OCI.OCI_OBJECT,
134
+ /*OCI.OCI_THREADED |*/ OCI.OCI_OBJECT,
78
135
  null,
79
136
  null,
80
137
  null,
@@ -126,10 +183,27 @@ public class OCIWrapper
126
183
  dpHandle = dpHandlePointer.getPointer(0);
127
184
  }
128
185
 
129
- public void prepareLoad(TableDefinition tableDefinition) throws SQLException
186
+ public void prepareLoad(TableDefinition tableDefinition, int bufferSize) throws SQLException
130
187
  {
131
188
  this.tableDefinition = tableDefinition;
132
189
 
190
+ check("OCIAttrSet(OCI_ATTR_BUF_SIZE)", oci.OCIAttrSet(
191
+ dpHandle,
192
+ OCI.OCI_HTYPE_DIRPATH_CTX,
193
+ createPointer(bufferSize),
194
+ 4,
195
+ OCI.OCI_ATTR_BUF_SIZE,
196
+ errHandle));
197
+
198
+ int numRows = Math.max(bufferSize / tableDefinition.getRowSize(), 16);
199
+ check("OCIAttrSet(OCI_ATTR_NUM_ROWS)", oci.OCIAttrSet(
200
+ dpHandle,
201
+ OCI.OCI_HTYPE_DIRPATH_CTX,
202
+ createPointer(numRows),
203
+ 4,
204
+ OCI.OCI_ATTR_NUM_ROWS,
205
+ errHandle));
206
+
133
207
  if (tableDefinition.getSchemaName() != null) {
134
208
  Pointer schemaName = createPointer(tableDefinition.getSchemaName());
135
209
  check("OCIAttrSet(OCI_ATTR_NAME)", oci.OCIAttrSet(
@@ -269,7 +343,6 @@ public class OCIWrapper
269
343
  dpstrHandle = dpstrHandlePointer.getPointer(0);
270
344
 
271
345
  Pointer maxRowCountPointer = createPointer(0);
272
-
273
346
  check("OCIAttrGet(OCI_ATTR_NUM_ROWS)", oci.OCIAttrGet(
274
347
  dpcaHandle,
275
348
  OCI.OCI_HTYPE_DIRPATH_COLUMN_ARRAY,
@@ -278,42 +351,31 @@ public class OCIWrapper
278
351
  OCI.OCI_ATTR_NUM_ROWS,
279
352
  errHandle));
280
353
  maxRowCount = maxRowCountPointer.getInt(0);
354
+ logger.info(String.format("OCI : DirectPathColumnArray.numRows = %,d", maxRowCount));
281
355
  }
282
356
 
283
- public void loadBuffer(RowBuffer rowBuffer) throws SQLException
284
- {
285
- Pointer pointer = new ByteBufferMemoryIO(Runtime.getSystemRuntime(), rowBuffer.getBuffer());
286
- short[] sizes = rowBuffer.getSizes();
357
+ public int getMaxRowCount() {
358
+ return maxRowCount;
359
+ }
287
360
 
288
- int i = 0;
289
- int position = 0;
290
- int rowCount = 0;
291
- for (int row = 0; row < rowBuffer.getRowCount(); row++) {
292
- for (short col = 0; col < tableDefinition.getColumnCount(); col++) {
293
- short size = sizes[i++];
361
+ public void loadBuffer(ByteBuffer buffer, ByteBuffer sizes, int rowCount) throws SQLException
362
+ {
363
+ logger.info(String.format("Loading %,d rows", rowCount));
364
+ long startTime = System.currentTimeMillis();
294
365
 
295
- check("OCIDirPathColArrayEntrySet", oci.OCIDirPathColArrayEntrySet(
296
- dpcaHandle,
297
- errHandle,
298
- rowCount,
299
- col,
300
- new BoundedMemoryIO(pointer, position, size),
301
- size,
302
- OCI.OCI_DIRPATH_COL_COMPLETE));
303
-
304
- position += size;
305
- }
366
+ check("OCIDirPathColArrayEntriesSet", bulkOci.embulk_output_oracle_OCIDirPathColArrayEntriesSet(
367
+ dpcaHandle,
368
+ errHandle,
369
+ (short)tableDefinition.getColumnCount(),
370
+ rowCount,
371
+ new ByteBufferMemoryIO(Runtime.getSystemRuntime(), buffer),
372
+ new ByteBufferMemoryIO(Runtime.getSystemRuntime(), sizes)));
306
373
 
307
- rowCount++;
308
- if (rowCount == maxRowCount) {
309
- loadRows(rowCount);
310
- rowCount = 0;
311
- }
312
- }
374
+ loadRows(rowCount);
313
375
 
314
- if (rowCount > 0) {
315
- loadRows(rowCount);
316
- }
376
+ totalRows += rowCount;
377
+ double seconds = (System.currentTimeMillis() - startTime) / 1000.0;
378
+ logger.info(String.format("> %.2f seconds (loaded %,d rows in total)", seconds, totalRows));
317
379
  }
318
380
 
319
381
  private void loadRows(int rowCount) throws SQLException
@@ -334,6 +396,7 @@ public class OCIWrapper
334
396
  check("OCIDirPathColArrayToStream", result);
335
397
  }
336
398
 
399
+ loadCount++;
337
400
  check("OCIDirPathLoadStream", oci.OCIDirPathLoadStream(
338
401
  dpHandle,
339
402
  dpstrHandle,
@@ -358,6 +421,9 @@ public class OCIWrapper
358
421
  public void commit() throws SQLException
359
422
  {
360
423
  committedOrRollbacked = true;
424
+ if (loadCount > 0) {
425
+ logger.info(String.format("OCI : OCIDirPathLoadStream : %,d rows x %,d times.", totalRows / loadCount, loadCount));
426
+ }
361
427
  logger.info("OCI : start to commit.");
362
428
 
363
429
  try {
@@ -0,0 +1,47 @@
1
+ package org.embulk.output.oracle.oci;
2
+
3
+ import jnr.ffi.Pointer;
4
+ import jnr.ffi.types.u_int16_t;
5
+ import jnr.ffi.types.u_int32_t;
6
+
7
+ public class PrimitiveBulkOCI implements BulkOCI {
8
+
9
+ private final OCI oci;
10
+
11
+
12
+ public PrimitiveBulkOCI(OCI oci)
13
+ {
14
+ this.oci = oci;
15
+ }
16
+
17
+ public short embulk_output_oracle_OCIDirPathColArrayEntriesSet(
18
+ Pointer dpca,
19
+ Pointer errhp,
20
+ @u_int16_t short columnCount,
21
+ @u_int32_t int rowCount,
22
+ Pointer data,
23
+ Pointer sizes) {
24
+
25
+ int index = 0;
26
+ long offset = 0;
27
+ for (int row = 0; row < rowCount; row++) {
28
+ for (short column = 0; column < columnCount; column++) {
29
+ short size = sizes.getShort(index++ * 2);
30
+ short result = oci.OCIDirPathColArrayEntrySet(
31
+ dpca,
32
+ errhp,
33
+ row,
34
+ column,
35
+ data.slice(offset),
36
+ size,
37
+ OCI.OCI_DIRPATH_COL_COMPLETE);
38
+ if (result != OCI.OCI_SUCCESS) {
39
+ return result;
40
+ }
41
+ offset += size;
42
+ }
43
+ }
44
+
45
+ return OCI.OCI_SUCCESS;
46
+ }
47
+ }
@@ -2,6 +2,7 @@ package org.embulk.output.oracle.oci;
2
2
 
3
3
  import java.math.BigDecimal;
4
4
  import java.nio.ByteBuffer;
5
+ import java.nio.ByteOrder;
5
6
  import java.nio.charset.Charset;
6
7
  import java.sql.SQLException;
7
8
 
@@ -12,48 +13,46 @@ import org.embulk.output.oracle.oci.TableDefinition;
12
13
 
13
14
  public class RowBuffer
14
15
  {
16
+ // this value was calculated by tests
17
+ private static final double OPTIMAL_LOAD_TIME = 10;
18
+ private static final int MIN_ROW_COUNT_TO_LOAD = 100;
19
+
20
+ private final OCIWrapper oci;
15
21
  private final TableDefinition table;
16
- private final int rowCount;
22
+ private final int maxRowCount;
17
23
 
18
- private int currentRow = 0;
24
+ private int rowCount = 0;
19
25
  private int currentColumn = 0;
20
26
 
21
- private final short[] sizes;
27
+ private int rowCountToLoad;
28
+ private int totalLoadedRowCount = 0;
29
+ private int loadCount = 0;
30
+ private double totalLoadTime = 0;
31
+
32
+ private final ByteBuffer sizes;
33
+ private final ByteBuffer defaultSizes;
22
34
  private final ByteBuffer buffer;
23
35
  private final ByteBuffer defaultBuffer;
24
36
 
25
- public RowBuffer(TableDefinition table, int rowCount)
37
+ public RowBuffer(OCIWrapper oci, TableDefinition table)
26
38
  {
39
+ this.oci = oci;
27
40
  this.table = table;
28
- this.rowCount = rowCount;
29
-
30
- int rowSize = 0;
31
- for (int i = 0; i < table.getColumnCount(); i++) {
32
- rowSize += table.getColumn(i).getDataSize();
33
- }
41
+ maxRowCount = oci.getMaxRowCount();
42
+ rowCountToLoad = maxRowCount;
34
43
 
44
+ ByteOrder byteOrder = Runtime.getSystemRuntime().byteOrder();
35
45
  // should be direct because used by native library
36
- buffer = ByteBuffer.allocateDirect(rowSize * rowCount).order(Runtime.getSystemRuntime().byteOrder());
46
+ buffer = ByteBuffer.allocateDirect(table.getRowSize() * maxRowCount).order(byteOrder);
37
47
  // position is not updated
38
- defaultBuffer = buffer.duplicate();
39
-
40
- sizes = new short[table.getColumnCount() * rowCount];
41
- }
42
-
43
- public ByteBuffer getBuffer() {
44
- return defaultBuffer;
45
- }
48
+ defaultBuffer = buffer.duplicate().order(byteOrder);
46
49
 
47
- public short[] getSizes() {
48
- return sizes;
50
+ sizes = ByteBuffer.allocateDirect(table.getColumnCount() * maxRowCount * 2).order(byteOrder);
51
+ defaultSizes = sizes.duplicate().order(byteOrder);
49
52
  }
50
53
 
51
- public void addValue(int value)
54
+ public void addValue(int value) throws SQLException
52
55
  {
53
- if (isFull()) {
54
- throw new IllegalStateException();
55
- }
56
-
57
56
  buffer.putInt(value);
58
57
 
59
58
  next((short)4);
@@ -61,10 +60,6 @@ public class RowBuffer
61
60
 
62
61
  public void addValue(String value) throws SQLException
63
62
  {
64
- if (isFull()) {
65
- throw new IllegalStateException();
66
- }
67
-
68
63
  ColumnDefinition column = table.getColumn(currentColumn);
69
64
  Charset charset = column.getCharset().getJavaCharset();
70
65
 
@@ -88,14 +83,18 @@ public class RowBuffer
88
83
  addValue(value.toPlainString());
89
84
  }
90
85
 
91
- private void next(short size)
86
+ private void next(short size) throws SQLException
92
87
  {
93
- sizes[currentRow * table.getColumnCount() + currentColumn] = size;
88
+ sizes.putShort(size);
94
89
 
95
90
  currentColumn++;
96
91
  if (currentColumn == table.getColumnCount()) {
97
92
  currentColumn = 0;
98
- currentRow++;
93
+ rowCount++;
94
+
95
+ if (rowCount >= rowCountToLoad) {
96
+ flush();
97
+ }
99
98
  }
100
99
  }
101
100
 
@@ -104,21 +103,26 @@ public class RowBuffer
104
103
  return currentColumn;
105
104
  }
106
105
 
107
- public int getRowCount()
108
- {
109
- return currentRow;
110
- }
111
-
112
- public boolean isFull()
106
+ public void flush() throws SQLException
113
107
  {
114
- return currentRow >= rowCount;
115
- }
108
+ if (rowCount > 0) {
109
+ synchronized (oci) {
110
+ long time = System.currentTimeMillis();
111
+ oci.loadBuffer(defaultBuffer, defaultSizes, rowCount);
112
+ totalLoadTime += System.currentTimeMillis() - time;
113
+ totalLoadedRowCount += rowCount;
114
+ loadCount += 1;
115
+ }
116
+
117
+ rowCount = 0;
118
+ currentColumn = 0;
119
+ buffer.clear();
120
+ sizes.clear();
116
121
 
117
- public void clear()
118
- {
119
- currentRow = 0;
120
- currentColumn = 0;
121
- buffer.clear();
122
+ if (loadCount >= 4) {
123
+ rowCountToLoad = Math.min(Math.max((int)(totalLoadedRowCount / totalLoadTime * OPTIMAL_LOAD_TIME), MIN_ROW_COUNT_TO_LOAD), maxRowCount);
124
+ }
125
+ }
122
126
  }
123
127
 
124
128
  }