Skip to content

Commit 58099d1

Browse files
authored
implements by option
* implements "by" read/write feature * add documentation for "by" read/write feature Signed-off-by: Jorge Aguilera <jorge@edn.es>
1 parent 5e04158 commit 58099d1

6 files changed

Lines changed: 149 additions & 5 deletions

File tree

docs/asciidoc/index.adoc

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,4 +139,28 @@ workflow{
139139
.toParquet( params.output, [record:AugmentedRecord])
140140
| view
141141
}
142-
----
142+
----
143+
144+
== Options
145+
146+
== splitParquet
147+
148+
=== record
149+
150+
"record" option allows specifying a *Record* java class instead of a raw Map. This option can improve parsing time as the
151+
reader can use only a subset of the fields to read
152+
153+
=== by
154+
155+
"by" option allows specifying a chunk size to read records.
156+
157+
== toParquet
158+
159+
=== record
160+
161+
"record" option allows specifying a *Record* java class instead of a raw Map. This option is required as the writer
162+
needs to know the structure of the record
163+
164+
=== by
165+
166+
"by" option allows specifying a chunk size to write records.

src/main/groovy/nextflow/parquet/ParquetExtension.groovy

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ class ParquetExtension extends PluginExtensionPoint {
7676
class ParquetSplitter {
7777
private DataflowWriteChannel target
7878
private Class<Record> clazz
79+
private List batchList = []
80+
private int sizeBatch = 0
7981

8082
ParquetSplitter(DataflowWriteChannel target, Map params) {
8183
this.target = target
@@ -85,6 +87,8 @@ class ParquetExtension extends PluginExtensionPoint {
8587
}
8688
this.clazz = params.record as Class<Record>
8789
}
90+
batchList = []
91+
sizeBatch = params.containsKey('by') && "$params.by".isNumber() ? params.by as int : 0
8892
}
8993

9094
void apply(Object source) {
@@ -93,7 +97,15 @@ class ParquetExtension extends PluginExtensionPoint {
9397
// create parquet reader
9498
final reader = new CarpetReader(toFile(source), clazz ?: Map)
9599
for (def record : reader) {
96-
target << record
100+
batchList << record
101+
if( batchList.size() >= sizeBatch ){
102+
target << (sizeBatch == 0 ? batchList.first() : batchList.toArray())
103+
batchList.clear()
104+
}
105+
}
106+
if( batchList.size() ){
107+
target << (sizeBatch == 0 ? batchList.first() : batchList.toArray())
108+
batchList.clear()
97109
}
98110
}
99111
catch( IOException e ) {
@@ -114,20 +126,33 @@ class ParquetExtension extends PluginExtensionPoint {
114126
class ParquetWriter implements Closeable{
115127
private Class<Record> clazz
116128
CarpetWriter writer
129+
private List batchList = []
130+
private int sizeBatch = 0
117131
ParquetWriter(String output, Map params){
118132
if( !params.record ||!(params.record instanceof Class<Record>)) {
119133
throw new IllegalArgumentException("A Record.class is required. Class provided $params.record")
120134
}
121135
this.clazz = params.record as Class<Record>
122136
var outputStream = new FileOutputStream(output)
123137
writer = new CarpetWriter<>(outputStream, this.clazz)
138+
batchList = []
139+
sizeBatch = params.containsKey('by') && "$params.by".isNumber() ? params.by as int : 0
124140
}
125141

126142
void write(Record record){
127-
writer.write(record)
143+
batchList << record
144+
if( batchList.size() >= sizeBatch ) {
145+
batchList.each { r ->
146+
writer.write(r)
147+
}
148+
batchList.clear()
149+
}
128150
}
129151

130152
void close(){
153+
batchList.each { r ->
154+
writer.write(r)
155+
}
131156
writer.close()
132157
}
133158
}

src/test/groovy/nextflow/parquet/PluginTest.groovy

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package nextflow.parquet
22

3+
import com.jerolba.carpet.CarpetReader
34
import nextflow.Channel
45
import nextflow.plugin.Plugins
56
import nextflow.plugin.TestPluginDescriptorFinder
@@ -119,4 +120,75 @@ class PluginTest extends Dsl2Spec{
119120
pathOutput.toFile().length()
120121
}
121122

123+
def 'should parse a parquet file in raw mode using by 1'() {
124+
given:
125+
def path = getClass().getResource('/multiple.parquet').toURI().path
126+
127+
when:
128+
def SCRIPT = """
129+
include {splitParquet} from 'plugin/nf-parquet'
130+
channel.fromPath("$path").splitParquet(by:1)
131+
""".toString()
132+
and:
133+
def result = new MockScriptRunner([:]).setScript(SCRIPT).execute()
134+
then:
135+
result.val == [[id: 1, name: "The 1 record"]]
136+
result.val == [[id: 2, name: "The 2 record"]]
137+
result.val == [[id: 3, name: "The 3 record"]]
138+
result.val == Channel.STOP
139+
}
140+
141+
def 'should parse a parquet file in raw mode using by 2'(){
142+
given:
143+
def path = getClass().getResource('/multiple.parquet').toURI().path
144+
when:
145+
def SCRIPT = """
146+
include {splitParquet} from 'plugin/nf-parquet'
147+
channel.fromPath("$path").splitParquet(by:2)
148+
""".toString()
149+
and:
150+
def result = new MockScriptRunner([:]).setScript(SCRIPT).execute()
151+
then:
152+
result.val == [ [id:1, name:"The 1 record"], [id:2, name:"The 2 record"] ]
153+
result.val == [ [id:3, name:"The 3 record"] ]
154+
result.val == Channel.STOP
155+
156+
}
157+
158+
def 'should write #total records to a file using by #by '(){
159+
when:
160+
def pathOutput = Files.createTempFile("", ".parquet")
161+
def SCRIPT = """
162+
include {splitParquet; toParquet} from 'plugin/nf-parquet'
163+
164+
import nextflow.parquet.DemoRecord
165+
166+
channel.of(1..$total)
167+
.map( { new DemoRecord(it, "The \$it record") } )
168+
.toParquet("$pathOutput", [record:DemoRecord, by:$by])
169+
.view()
170+
""".toString()
171+
and:
172+
def result = new MockScriptRunner([:]).setScript(SCRIPT).execute()
173+
def values = (1..total).collect{ result.val }
174+
then:
175+
values.size() == total
176+
result.val == Channel.STOP
177+
178+
when:
179+
final reader = new CarpetReader(pathOutput.toFile(), Map)
180+
int count = 0
181+
for (def record : reader) {
182+
println record
183+
count++
184+
}
185+
then:
186+
count == total
187+
188+
where:
189+
by | total
190+
1 | 100
191+
10 | 100
192+
100 | 100
193+
}
122194
}
616 Bytes
Binary file not shown.

validation/read_process.nf

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
include { splitParquet } from 'plugin/nf-parquet'
2+
3+
import myrecords.*
4+
5+
6+
process processParquetChunk {
7+
input:
8+
val(input_chunk_parquet)
9+
output:
10+
stdout
11+
script:
12+
"""
13+
echo ${input_chunk_parquet}
14+
"""
15+
}
16+
17+
workflow{
18+
19+
channel.fromPath("*.parquet").splitParquet(by:100)
20+
| processParquetChunk
21+
| view
22+
23+
}

validation/read_raw.nf

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ include { splitParquet } from 'plugin/nf-parquet'
22

33
import myrecords.*
44

5-
channel.fromPath("*.parquet").splitParquet()
6-
| view
5+
channel.fromPath("*.parquet").splitParquet(by:1)
6+
| view

0 commit comments

Comments
 (0)