1+ package org .jsmart .zerocode .core .db ;
2+
3+ import java .sql .Connection ;
4+ import java .sql .SQLException ;
5+ import java .util .ArrayList ;
6+ import java .util .Arrays ;
7+ import java .util .List ;
8+ import java .util .stream .Collectors ;
9+ import java .util .stream .IntStream ;
10+
11+ import org .apache .commons .dbutils .QueryRunner ;
12+ import org .apache .commons .lang3 .StringUtils ;
13+ import org .slf4j .Logger ;
14+ import org .slf4j .LoggerFactory ;
15+
16+ import com .univocity .parsers .csv .CsvParser ;
17+
18+ class DbCsvLoader {
19+ private static final Logger LOGGER = LoggerFactory .getLogger (DbCsvLoader .class );
20+ private Connection conn ;
21+ private CsvParser csvParser ;
22+
23+ public DbCsvLoader (Connection conn , CsvParser csvParser ) {
24+ this .conn = conn ;
25+ this .csvParser = csvParser ;
26+ }
27+
28+ /**
29+ * Loads rows in csv format (csvLines) into a table in the database
30+ * and returns the total number of rows
31+ */
32+ int loadCsv (String table , List <String > csvLines , boolean withHeaders , String nullString ) throws SQLException {
33+ if (csvLines == null || csvLines .isEmpty ())
34+ return 0 ;
35+
36+ List <String []> lines = parseLines (table , csvLines );
37+
38+ String [] headers = buildHeaders (lines .get (0 ), withHeaders );
39+ List <Object []> paramset = buildParameters (table , headers , lines , withHeaders , nullString );
40+ if (paramset .isEmpty ()) // can have headers, but no rows
41+ return 0 ;
42+
43+ String sql = buildSql (table , headers , paramset .get (0 ).length );
44+ LOGGER .info ("Loading CSV using this sql: {}" , sql );
45+
46+ QueryRunner runner = new QueryRunner ();
47+ int insertCount = 0 ;
48+ for (int i = 0 ; i < paramset .size (); i ++) {
49+ insertRow (runner , i , sql , paramset .get (i ));
50+ insertCount ++;
51+ }
52+ LOGGER .info ("Total of rows inserted: {}" , insertCount );
53+ return insertCount ;
54+ }
55+
56+ private List <String []> parseLines (String table , List <String > lines ) {
57+ int numCol = 0 ; // will check that every row has same columns than the first
58+ List <String []> parsedLines = new ArrayList <>();
59+ for (int i = 0 ; i <lines .size (); i ++) {
60+ String [] parsedLine = csvParser .parseLine (lines .get (i ));
61+ parsedLines .add (parsedLine );
62+ if (i == 0 ) {
63+ numCol =parsedLine .length ;
64+ } else if (numCol != parsedLine .length ) {
65+ String message = String .format ("Error parsing CSV content to load into table %s: "
66+ + "Row %d has %d columns and should have %d" , table , i + 1 , parsedLine .length , numCol );
67+ LOGGER .error (message );
68+ throw new RuntimeException (message );
69+ }
70+ }
71+ return parsedLines ;
72+ }
73+
74+ private String [] buildHeaders (String [] line , boolean withHeaders ) {
75+ return withHeaders ? line : new String [] {};
76+ }
77+
78+ private List <Object []> buildParameters (String table , String [] headers , List <String []> lines , boolean withHeaders , String nullString ) {
79+ DbValueConverter converter = new DbValueConverter (conn , table );
80+ List <Object []> paramset = new ArrayList <>();
81+ for (int i = withHeaders ? 1 : 0 ; i < lines .size (); i ++) {
82+ String [] parsedLine = lines .get (i );
83+ parsedLine = processNulls (parsedLine , nullString );
84+ Object [] params ;
85+ try {
86+ params = converter .convertColumnValues (headers , parsedLine );
87+ LOGGER .info (" row [{}] params: {}" , i + 1 , Arrays .asList (params ).toString ());
88+ } catch (Exception e ) { // Not only SQLException as converter also does parsing
89+ String message = String .format ("Error matching data type of parameters and table columns at CSV row %d" , i + 1 );
90+ LOGGER .error (message ); // do not log the exception because it will be logged by the parent executor (DbCsvLoader)
91+ LOGGER .error ("Exception message: {}" , e .getMessage ());
92+ throw new RuntimeException (message , e );
93+ }
94+ paramset .add (params );
95+ }
96+ return paramset ;
97+ }
98+
99+ private String [] processNulls (String [] line , String nullString ) {
100+ for (int i = 0 ; i < line .length ; i ++) {
101+ if (StringUtils .isBlank (nullString ) && StringUtils .isBlank (line [i ])) {
102+ line [i ] = null ;
103+ } else if (!StringUtils .isBlank (nullString )) {
104+ if (StringUtils .isBlank (line [i ])) // null must be empty string
105+ line [i ] = "" ;
106+ else if (nullString .trim ().equalsIgnoreCase (line [i ].trim ()))
107+ line [i ] = null ;
108+ }
109+ }
110+ return line ;
111+ }
112+
113+ private String buildSql (String table , String [] headers , int columnCount ) {
114+ String placeholders = IntStream .range (0 , columnCount )
115+ .mapToObj (i -> "?" ).collect (Collectors .joining ("," ));
116+ return "INSERT INTO " + table
117+ + (headers .length > 0 ? " (" + String .join ("," , headers ) + ")" : "" )
118+ + " VALUES (" + placeholders + ");" ;
119+ }
120+
121+ private void insertRow (QueryRunner runner , int rowId , String sql , Object [] params ) {
122+ try {
123+ runner .update (conn , sql , params );
124+ } catch (SQLException e ) {
125+ String message = String .format ("Error inserting data at CSV row %d" , rowId + 1 );
126+ LOGGER .error (message ); // do not log the exception because it will be logged by the parent executor (DbCsvLoader)
127+ LOGGER .error ("Exception message: {}" , e .getMessage ());
128+ throw new RuntimeException (message , e );
129+ }
130+ }
131+
132+ }
0 commit comments