@Override
  protected ResultSetFuture newFuture() {
    BoundStatement bound =
        factory
            .preparedStatement
            .bind()
            .setUUID("ts_uuid", input.ts_uuid())
            .setString("trace_id", input.trace_id())
            .setString("id", input.id());

    // now set the nullable fields
    if (null != input.trace_id_high()) bound.setString("trace_id_high", input.trace_id_high());
    if (null != input.parent_id()) bound.setString("parent_id", input.parent_id());
    if (null != input.kind()) bound.setString("kind", input.kind());
    if (null != input.span()) bound.setString("span", input.span());
    if (0L != input.ts()) bound.setLong("ts", input.ts());
    if (0L != input.duration()) bound.setLong("duration", input.duration());
    if (null != input.l_ep()) bound.set("l_ep", input.l_ep(), EndpointUDT.class);
    if (null != input.r_ep()) bound.set("r_ep", input.r_ep(), EndpointUDT.class);
    if (!input.annotations().isEmpty()) bound.setList("annotations", input.annotations());
    if (!input.tags().isEmpty()) bound.setMap("tags", input.tags());
    if (input.shared()) bound.setBool("shared", true);
    if (input.debug()) bound.setBool("debug", true);

    if (factory.searchEnabled) {
      if (null != input.l_ep()) bound.setString("l_service", input.l_ep().getService());
      if (null != input.annotation_query()) {
        bound.setString("annotation_query", input.annotation_query());
      }
    }
    return factory.session.executeAsync(bound);
  }
@ Override protected ResultSetFuture newFuture ( ) { BoundStatement bound = factory . preparedStatement . bind ( ) . setUUID ( "ts_uuid" , input . ts_uuid ( ) ) . setString ( "trace_id" , input . trace_id ( ) ) . setString ( "id" , input . id ( ) ) ; if ( null != input . trace_id_high ( ) ) bound . setString ( "trace_id_high" , input . trace_id_high ( ) ) ; if ( null != input . parent_id ( ) ) bound . setString ( "parent_id" , input . parent_id ( ) ) ; if ( null != input . kind ( ) ) bound . setString ( "kind" , input . kind ( ) ) ; if ( null != input . span ( ) ) bound . setString ( "span" , input . span ( ) ) ; if ( 0L != input . ts ( ) ) bound . setLong ( "ts" , input . ts ( ) ) ; if ( 0L != input . duration ( ) ) bound . setLong ( "duration" , input . duration ( ) ) ; if ( null != input . l_ep ( ) ) bound . set ( "l_ep" , input . l_ep ( ) , EndpointUDT . class ) ; if ( null != input . r_ep ( ) ) bound . set ( "r_ep" , input . r_ep ( ) , EndpointUDT . class ) ; if ( ! input . annotations ( ) . isEmpty ( ) ) bound . setList ( "annotations" , input . annotations ( ) ) ; if ( ! input . tags ( ) . isEmpty ( ) ) bound . setMap ( "tags" , input . tags ( ) ) ; if ( input . shared ( ) ) bound . setBool ( "shared" , true ) ; if ( input . debug ( ) ) bound . setBool ( "debug" , true ) ; if ( factory . searchEnabled ) { if ( null != input . l_ep ( ) ) bound . setString ( "l_service" , input . l_ep ( ) . getService ( ) ) ; if ( null != input . annotation_query ( ) ) { bound . setString ( "annotation_query" , input . annotation_query ( ) ) ; } } return factory . session . executeAsync ( bound ) ; }
TLDR: we are guarding against setting null, as doing so implies tombstones. We are dodging setX
to keep code simpler than other alternatives described below.

<p>If there's consistently 8 tombstones (nulls) per row, then we'll only need 125 spans in a
trace (rows in a partition) to trigger the `tombstone_warn_threshold warnings being logged in
the C* nodes. And if we go to 12500 spans in a trace then that whole trace partition would
become unreadable. Cassandra warns at a 1000 tombstones in any query, and fails on 100000
tombstones.

<p>There's also a small question about disk usage efficiency. Each tombstone is a cell name and
basically empty cell value entry stored on disk. Given that the cells are, apart from tags and
annotations, generally very small then this could be proportionally an unnecessary waste of
disk.

<p>To avoid this relying upon a number of variant prepared statements for inserting a span is
the normal practice.

<p>Another popular practice is to insert those potentially null columns as separate statements
(and optionally put them together into UNLOGGED batches). This works as multiple writes to the
same partition has little overhead, and here we're not worried about lack of isolation between
those writes, as the write is asynchronous anyway. An example of this approach is in the
cassandra-reaper project here:
https://github.com/thelastpickle/cassandra-reaper/blob/master/src/server/src/main/java/io/cassandrareaper/storage/CassandraStorage.java#L622-L642