2026-03-31 15:32:36 +02:00
# include "file.h"
# include "logger.h"
2024-08-23 20:29:07 +02:00
2025-09-14 13:53:20 +02:00
# ifdef IFOPSH_WITH_ROCKSDB
2025-09-03 11:11:55 +02:00
# include <rocksdb/table.h>
2025-09-10 21:42:30 +02:00
# include <rocksdb/convenience.h>
2025-09-14 13:53:20 +02:00
# endif
2025-09-03 11:11:55 +02:00
2025-09-29 11:29:07 +02:00
# include <fstream>
# include <sys/types.h>
# include <sys/stat.h>
2026-03-31 15:32:36 +02:00
ifcopenshell : : parse_context : : ~ parse_context ( ) {
2024-08-23 20:29:07 +02:00
for ( auto & t : tokens_ ) {
2025-08-25 12:45:10 +02:00
std : : visit ( [ ] ( auto & v ) {
2024-08-23 20:29:07 +02:00
if constexpr ( std : : is_same_v < std : : decay_t < decltype ( v ) > , parse_context * > ) {
delete v ;
}
} , t ) ;
}
}
2026-03-31 15:32:36 +02:00
ifcopenshell : : parse_context & ifcopenshell : : parse_context : : push ( ) {
2026-04-22 12:03:58 +02:00
auto * pc = new parse_context ;
tokens_ . push_back ( pc ) ;
return * pc ;
2024-08-23 20:29:07 +02:00
}
2026-03-31 15:32:36 +02:00
void ifcopenshell : : parse_context : : push ( token t ) {
2024-08-23 20:29:07 +02:00
tokens_ . push_back ( t ) ;
}
2026-03-31 15:32:36 +02:00
void ifcopenshell : : parse_context : : push ( const express : : Base & inst ) {
2024-08-23 20:29:07 +02:00
tokens_ . push_back ( inst ) ;
}
namespace {
template < typename Variant , typename T >
struct is_type_in_variant ;
2024-08-24 13:24:23 +02:00
// Specialization when there are multiple types in the variant
template < typename T , typename First , typename . . . Rest >
2025-08-25 12:45:10 +02:00
struct is_type_in_variant < std : : variant < First , Rest . . . > , T >
2024-08-23 20:29:07 +02:00
{
2025-08-25 12:45:10 +02:00
static constexpr bool value = std : : is_same < T , First > : : value | | is_type_in_variant < std : : variant < Rest . . . > , T > : : value ;
2024-08-24 13:24:23 +02:00
} ;
// Specialization when there is only one type left in the variant
template < typename T , typename Last >
2025-08-25 12:45:10 +02:00
struct is_type_in_variant < std : : variant < Last > , T >
2024-08-24 13:24:23 +02:00
{
static constexpr bool value = std : : is_same < T , Last > : : value ;
2024-08-23 20:29:07 +02:00
} ;
template < typename Variant , typename T >
constexpr bool is_type_in_variant_v = is_type_in_variant < Variant , T > : : value ;
template < typename Fn >
2026-03-31 15:32:36 +02:00
void dispatch_token ( std : : optional < size_t > instance_id , int attribute_id , ifcopenshell : : token t , ifcopenshell : : declaration * decl , Fn fn ) {
2026-03-26 15:49:28 +01:00
if ( t . is_binary ( ) ) {
fn ( t . as_binary ( ) ) ;
} else if ( t . is_bool ( ) ) {
fn ( t . as_bool ( ) ) ;
} else if ( t . is_logical ( ) ) {
fn ( t . as_logical ( ) ) ;
} else if ( t . is_enumeration ( ) ) {
const auto & s = t . as_string ( ) ;
2024-11-11 13:57:36 +01:00
if ( decl & & decl - > as_enumeration_type ( ) ) {
2024-08-23 20:29:07 +02:00
try {
2026-03-31 15:32:36 +02:00
fn ( enumeration_reference ( decl - > as_enumeration_type ( ) , decl - > as_enumeration_type ( ) - > lookup_enum_offset ( s ) ) ) ;
} catch ( ifcopenshell : : exception & e ) {
logger : : error ( " An enumeration literal ' " + s + " ' is not valid for type ' " + decl - > name ( ) + " ' at offset " + std : : to_string ( t . start_pos ) ) ;
2024-08-23 20:29:07 +02:00
}
2024-11-11 13:57:36 +01:00
} else {
2026-03-31 15:32:36 +02:00
logger : : error ( " An enumeration literal ' " + s + " ' is not expected at attribute index ' " + std : : to_string ( attribute_id ) + " ' at offset " + std : : to_string ( t . start_pos ) ) ;
2024-08-23 20:29:07 +02:00
}
2026-03-26 15:49:28 +01:00
} else if ( t . is_int ( ) ) {
// @nb make sure is_int() comes before is_float()
fn ( t . as_int ( ) ) ;
} else if ( t . is_float ( ) ) {
fn ( t . as_float ( ) ) ;
} else if ( t . is_identifier ( ) ) {
2026-03-31 15:32:36 +02:00
fn ( ifcopenshell : : reference_or_simple_type { ifcopenshell : : instance_reference { ( int ) t . as_identifier ( ) , t . start_pos } } ) ;
2026-03-26 15:49:28 +01:00
} else if ( t . is_string ( ) ) {
fn ( t . as_string ( ) ) ;
} else if ( t . is_operator ( ' * ' ) ) {
2024-08-23 20:29:07 +02:00
// This is only in place for the validator
2026-03-31 15:32:36 +02:00
fn ( derived { } ) ;
2024-08-23 20:29:07 +02:00
}
}
template < size_t Depth , typename Fn >
2026-03-31 15:32:36 +02:00
void construct_ ( std : : optional < size_t > instance_id , int attribute_id , ifcopenshell : : parse_context & p , const ifcopenshell : : aggregation_type * aggr , Fn fn ) {
2024-08-23 20:29:07 +02:00
if ( p . tokens_ . empty ( ) ) {
// @todo instead of ugly if-else we could also default initialize the respective
// variant types below.
if ( aggr ) {
2026-03-31 15:32:36 +02:00
auto aggr_type = ifcopenshell : : make_aggregate ( ifcopenshell : : from_parameter_type ( aggr - > type_of_element ( ) ) ) ;
if ( aggr_type = = ifcopenshell : : Argument_AGGREGATE_OF_INT ) {
2024-08-23 20:29:07 +02:00
fn ( std : : vector < int > { } ) ;
2026-03-31 15:32:36 +02:00
} else if ( aggr_type = = ifcopenshell : : Argument_AGGREGATE_OF_DOUBLE ) {
2024-08-23 20:29:07 +02:00
fn ( std : : vector < double > { } ) ;
2026-03-31 15:32:36 +02:00
} else if ( aggr_type = = ifcopenshell : : Argument_AGGREGATE_OF_STRING ) {
2024-08-23 20:29:07 +02:00
fn ( std : : vector < std : : string > { } ) ;
2026-03-31 15:32:36 +02:00
} else if ( aggr_type = = ifcopenshell : : Argument_AGGREGATE_OF_BINARY ) {
2024-08-23 20:29:07 +02:00
fn ( std : : vector < boost : : dynamic_bitset < > > { } ) ;
2026-03-31 15:32:36 +02:00
} else if ( aggr_type = = ifcopenshell : : Argument_AGGREGATE_OF_ENTITY_INSTANCE ) {
2026-01-04 10:40:02 +01:00
fn ( std : : vector < express : : Base > { } ) ;
2026-03-31 15:32:36 +02:00
} else if ( aggr_type = = ifcopenshell : : Argument_AGGREGATE_OF_AGGREGATE_OF_INT ) {
2024-08-23 20:29:07 +02:00
fn ( std : : vector < std : : vector < int > > { } ) ;
2026-03-31 15:32:36 +02:00
} else if ( aggr_type = = ifcopenshell : : Argument_AGGREGATE_OF_AGGREGATE_OF_DOUBLE ) {
2024-08-23 20:29:07 +02:00
fn ( std : : vector < std : : vector < double > > { } ) ;
2026-03-31 15:32:36 +02:00
} else if ( aggr_type = = ifcopenshell : : Argument_AGGREGATE_OF_AGGREGATE_OF_ENTITY_INSTANCE ) {
2026-01-04 10:40:02 +01:00
fn ( std : : vector < std : : vector < express : : Base > > { } ) ;
2024-08-23 20:29:07 +02:00
}
}
return ;
}
2025-08-25 12:45:10 +02:00
typedef std : : variant <
2026-03-31 15:32:36 +02:00
blank ,
2024-08-23 20:29:07 +02:00
std : : vector < int > ,
std : : vector < double > ,
std : : vector < std : : string > ,
std : : vector < boost : : dynamic_bitset < > > ,
2026-03-31 15:32:36 +02:00
std : : vector < ifcopenshell : : reference_or_simple_type > ,
2024-08-23 20:29:07 +02:00
std : : vector < std : : vector < int > > ,
std : : vector < std : : vector < double > > ,
2026-03-31 15:32:36 +02:00
std : : vector < std : : vector < ifcopenshell : : reference_or_simple_type > >
2024-08-23 20:29:07 +02:00
> possible_aggregation_types_t ;
possible_aggregation_types_t aggregate_storage ;
auto append_to_aggregate_storage = [ & aggregate_storage ] ( const auto & v ) {
if constexpr ( is_type_in_variant_v < possible_aggregation_types_t , std : : vector < std : : decay_t < decltype ( v ) > > > ) {
2025-08-25 12:45:10 +02:00
if ( aggregate_storage . index ( ) = = 0 ) {
2024-08-23 20:29:07 +02:00
aggregate_storage = std : : vector < std : : decay_t < decltype ( v ) > > { v } ;
} else {
2025-08-25 12:45:10 +02:00
if ( auto * vec_ptr = std : : get_if < std : : vector < std : : decay_t < decltype ( v ) > > > ( & aggregate_storage ) ) {
2024-08-23 20:29:07 +02:00
vec_ptr - > push_back ( v ) ;
} else {
2024-09-11 20:15:14 +02:00
if constexpr ( std : : is_same_v < std : : decay_t < decltype ( v ) > , int > ) {
2025-08-25 12:45:10 +02:00
auto * vec_ptr2 = std : : get_if < std : : vector < double > > ( & aggregate_storage ) ;
2024-09-11 20:15:14 +02:00
if ( vec_ptr2 ) {
// double[] + int
vec_ptr2 - > push_back ( ( double ) v ) ;
}
}
if constexpr ( std : : is_same_v < std : : decay_t < decltype ( v ) > , double > ) {
2025-08-25 12:45:10 +02:00
auto * vec_ptr2 = std : : get_if < std : : vector < int > > ( & aggregate_storage ) ;
2024-09-11 20:15:14 +02:00
if ( vec_ptr2 ) {
// int[] -> double[] + double
std : : vector < double > ps ( vec_ptr2 - > begin ( ) , vec_ptr2 - > end ( ) ) ;
ps . push_back ( v ) ;
aggregate_storage = ps ;
}
}
if constexpr ( std : : is_same_v < std : : decay_t < decltype ( v ) > , std : : vector < int > > ) {
2025-08-25 12:45:10 +02:00
auto * vec_ptr2 = std : : get_if < std : : vector < std : : vector < double > > > ( & aggregate_storage ) ;
2024-09-11 20:15:14 +02:00
if ( vec_ptr2 ) {
// double[][] + int[]
std : : vector < double > vd ( v . begin ( ) , v . end ( ) ) ;
vec_ptr2 - > push_back ( vd ) ;
}
}
if constexpr ( std : : is_same_v < std : : decay_t < decltype ( v ) > , std : : vector < double > > ) {
2025-08-25 12:45:10 +02:00
auto * vec_ptr2 = std : : get_if < std : : vector < std : : vector < int > > > ( & aggregate_storage ) ;
2024-09-11 20:15:14 +02:00
if ( vec_ptr2 ) {
// int[][] -> double[][] + double[]
std : : vector < std : : vector < double > > vvd ;
for ( auto & vv : * vec_ptr2 ) {
std : : vector < double > vd ( vv . begin ( ) , vv . end ( ) ) ;
vvd . push_back ( vd ) ;
}
vvd . push_back ( v ) ;
aggregate_storage = vvd ;
}
}
2024-08-23 20:29:07 +02:00
// @todo would be cool if we can trace this back to file offset
2025-08-25 12:45:10 +02:00
auto current = std : : visit ( [ ] ( auto v ) {
2026-03-31 15:32:36 +02:00
if constexpr ( ! std : : is_same_v < decltype ( v ) , blank > ) {
2024-08-23 20:29:07 +02:00
return std : : string ( typeid ( typename decltype ( v ) : : value_type ) . name ( ) ) ;
} else {
// Cannot occur as aggregate_storage.which() == 0
// is another branch several statements up. But is
// needed for consistency of return type.
return std : : string { } ;
}
} , aggregate_storage ) ;
2024-09-11 20:15:14 +02:00
2026-03-31 15:32:36 +02:00
logger : : error ( " Inconsistent aggregate valuation while attempting to append " + std : : string ( typeid ( decltype ( v ) ) . name ( ) ) + " to an aggregate of " + current ) ;
2024-08-23 20:29:07 +02:00
// @todo boolean -> logical upgrade
// wait a second... there are no aggregate of bool / logical in the schema..
//
// if constexpr (std::is_same_v<std::decay_t<decltype(v)>, bool>) {
// auto* vec_ptr = boost::get<std::vector<boost::tribool>(&aggregate_storage);
// vec_ptr->push_back(v);
// }
// if constexpr (std::is_same_v<std::decay_t<decltype(v)>, boost::tribool>) {
// auto* vec_ptr = boost::get<std::vector<bool>(&aggregate_storage);
// std::vector<boost::tribool> ps(vec_ptr->begin(), vec_ptr->end());
// ps.push_back(v);
// aggregate_storage = ps;
// }
}
}
} else {
// @todo would be cool if we can trace this back to file offset
2026-03-31 15:32:36 +02:00
logger : : error ( std : : string ( " Aggregates of " ) + typeid ( decltype ( v ) ) . name ( ) + " are not supported in the IfcOpenShell parser " ) ;
2024-08-23 20:29:07 +02:00
}
} ;
for ( auto & t : p . tokens_ ) {
2025-08-25 12:45:10 +02:00
std : : visit ( [ & aggregate_storage , & append_to_aggregate_storage , aggr , instance_id , attribute_id ] ( const auto & v ) {
2026-03-31 15:32:36 +02:00
if constexpr ( std : : is_same_v < std : : decay_t < decltype ( v ) > , ifcopenshell : : token > ) {
2024-08-23 20:29:07 +02:00
// @todo get aggregate of enumeration
2024-11-21 15:16:56 +01:00
dispatch_token ( instance_id , attribute_id , v , aggr & & aggr - > type_of_element ( ) - > as_named_type ( ) ? aggr - > type_of_element ( ) - > as_named_type ( ) - > declared_type ( ) : nullptr , append_to_aggregate_storage ) ;
2026-04-22 12:03:58 +02:00
} else if constexpr ( std : : is_same_v < std : : decay_t < decltype ( v ) > , ifcopenshell : : parse_context * > ) {
2024-08-23 20:29:07 +02:00
// nested list
if constexpr ( Depth < 3 ) {
2024-11-21 15:16:56 +01:00
construct_ < Depth + 1 > ( instance_id , attribute_id , * v , nullptr , append_to_aggregate_storage ) ;
2024-08-23 20:29:07 +02:00
}
} else {
2026-03-31 15:32:36 +02:00
append_to_aggregate_storage ( ifcopenshell : : reference_or_simple_type { v } ) ;
2024-08-23 20:29:07 +02:00
}
} , t ) ;
}
2025-08-25 12:45:10 +02:00
std : : visit ( fn , aggregate_storage ) ;
2024-08-23 20:29:07 +02:00
}
}
2026-03-31 15:32:36 +02:00
std : : shared_ptr < instance_data > ifcopenshell : : parse_context : : construct ( ifcopenshell : : file * owner , std : : optional < size_t > name , unresolved_references & references_to_resolve , const ifcopenshell : : declaration * decl , std : : optional < size_t > expected_size , int resolve_reference_index , bool coerce_attribute_count ) {
std : : vector < const ifcopenshell : : parameter_type * > parameter_types ;
std : : unique_ptr < ifcopenshell : : named_type > transient_named_type ;
2024-08-23 20:29:07 +02:00
if ( ( decl ! = nullptr ) & & ( decl - > as_type_declaration ( ) ! = nullptr ) ) {
parameter_types = { decl - > as_type_declaration ( ) - > declared_type ( ) } ;
} else if ( ( decl ! = nullptr ) & & ( decl - > as_enumeration_type ( ) ! = nullptr ) ) {
2026-03-31 15:32:36 +02:00
transient_named_type . reset ( new ifcopenshell : : named_type ( const_cast < ifcopenshell : : declaration * > ( decl ) ) ) ;
2024-08-23 20:29:07 +02:00
parameter_types = { & * transient_named_type } ;
} else if ( ( decl ! = nullptr ) & & ( decl - > as_entity ( ) ! = nullptr ) ) {
2026-03-27 20:45:13 +01:00
const auto & entity_attrs = decl - > as_entity ( ) - > all_attributes ( ) ;
2024-08-23 20:29:07 +02:00
std : : transform (
entity_attrs . begin ( ) ,
entity_attrs . end ( ) ,
std : : back_inserter ( parameter_types ) ,
[ ] ( auto * attr ) {
return attr - > type_of_attribute ( ) ;
}
) ;
}
if ( ( ( decl ! = nullptr ) & & ( tokens_ . size ( ) ! = parameter_types . size ( ) ) ) | |
expected_size & & * expected_size ! = tokens_ . size ( ) )
{
size_t expected = expected_size ? * expected_size : parameter_types . size ( ) ;
2025-10-25 21:12:12 +02:00
if ( decl ! = nullptr & & decl - > schema ( ) = = & Header_section_schema : : get_schema ( ) ) {
2026-03-31 15:32:36 +02:00
logger : : warning ( " Expected " + std : : to_string ( expected ) + " attribute values, found " + std : : to_string ( tokens_ . size ( ) ) + " for header entity " + decl - > name ( ) ) ;
2025-10-25 21:12:12 +02:00
} else {
2026-03-31 15:32:36 +02:00
logger : : warning ( " Expected " + std : : to_string ( expected ) + " attribute values, found " + std : : to_string ( tokens_ . size ( ) ) + ( name ? std : : string ( " for instance # " + std : : to_string ( * name ) ) : std : : string ( " " ) ) ) ;
2025-10-25 21:12:12 +02:00
}
2024-08-23 20:29:07 +02:00
}
if ( tokens_ . empty ( ) ) {
2026-03-31 15:32:36 +02:00
return std : : make_shared < instance_data > ( owner , decl , name . value_or ( 0 ) , in_memory_attribute_storage ( 0 ) ) ;
2024-08-23 20:29:07 +02:00
}
2025-08-28 11:15:05 +02:00
in_memory_attribute_storage storage ( coerce_attribute_count
? ( decl ! = nullptr
2024-08-23 20:29:07 +02:00
? ( std : : min ) ( parameter_types . size ( ) , tokens_ . size ( ) )
2025-08-28 11:15:05 +02:00
: tokens_ . size ( ) )
: tokens_ . size ( )
2024-08-23 20:29:07 +02:00
) ;
auto it = tokens_ . begin ( ) ;
auto kt = parameter_types . begin ( ) ;
for ( ; it ! = tokens_ . end ( ) & & ( ( decl = = nullptr ) | | kt ! = parameter_types . end ( ) ) ; + + it ) {
auto & token = * it ;
// @todo coerce to expected type, e.g empty -> std::vector<int>, bool -> logical
2026-03-31 15:32:36 +02:00
const ifcopenshell : : parameter_type * param_type = nullptr ;
2024-08-23 20:29:07 +02:00
if ( decl ! = nullptr ) {
param_type = * kt ;
}
auto index = ( uint8_t ) std : : distance ( tokens_ . begin ( ) , it ) ;
2025-08-26 10:17:57 +02:00
std : : visit ( [ this , & storage , name , & references_to_resolve , index , param_type , resolve_reference_index ] ( const auto & v ) {
2026-03-31 15:32:36 +02:00
if constexpr ( std : : is_same_v < std : : decay_t < decltype ( v ) > , ifcopenshell : : token > ) {
2025-04-11 15:52:40 +02:00
dispatch_token ( name , index , v , param_type & & param_type - > as_named_type ( ) ? param_type - > as_named_type ( ) - > declared_type ( ) : nullptr , [ this , & storage , name , & references_to_resolve , index , resolve_reference_index ] ( auto v ) {
2026-03-31 15:32:36 +02:00
if constexpr ( std : : is_same_v < std : : decay_t < decltype ( v ) > , ifcopenshell : : reference_or_simple_type > ) {
2025-10-13 20:47:43 +02:00
if ( name ) {
2024-10-20 14:17:13 +02:00
references_to_resolve . push_back ( std : : make_pair (
// @todo previously this was storage but apparently the
// pointer is not constant with the moving and temporary nature
// maybe it ought to be and in that case a pointer is more direct
2026-03-31 15:32:36 +02:00
mutable_attribute_value { ( uint32_t ) * name , resolve_reference_index = = - 1 ? index : ( uint8_t ) resolve_reference_index } ,
2024-10-20 14:17:13 +02:00
v
) ) ;
}
2024-08-23 20:29:07 +02:00
} else {
storage . set ( index , v ) ;
}
} ) ;
2026-04-22 12:03:58 +02:00
} else if constexpr ( std : : is_same_v < std : : decay_t < decltype ( v ) > , ifcopenshell : : parse_context * > ) {
2024-08-23 20:29:07 +02:00
const auto * pt = param_type ;
if ( pt ) {
while ( pt - > as_named_type ( ) & & pt - > as_named_type ( ) - > declared_type ( ) - > as_type_declaration ( ) ) {
pt = pt - > as_named_type ( ) - > declared_type ( ) - > as_type_declaration ( ) - > declared_type ( ) ;
}
}
2025-04-11 15:52:40 +02:00
construct_ < 0 > ( name , index , * v , pt ? pt - > as_aggregation_type ( ) : nullptr , [ this , & storage , name , & references_to_resolve , index , resolve_reference_index ] ( const auto & v ) {
2024-08-23 20:29:07 +02:00
if constexpr ( std : : is_same_v < std : : decay_t < decltype ( v ) > , std : : vector < reference_or_simple_type > > ) {
2025-10-13 20:47:43 +02:00
if ( name ) {
references_to_resolve . push_back ( { { ( uint32_t ) * name , resolve_reference_index = = - 1 ? index : ( uint8_t ) resolve_reference_index } , v } ) ;
2024-10-20 14:17:13 +02:00
}
2024-08-23 20:29:07 +02:00
} else if constexpr ( std : : is_same_v < std : : decay_t < decltype ( v ) > , std : : vector < std : : vector < reference_or_simple_type > > > ) {
2025-10-13 20:47:43 +02:00
if ( name ) {
references_to_resolve . push_back ( { { ( uint32_t ) * name , resolve_reference_index = = - 1 ? index : ( uint8_t ) resolve_reference_index } , v } ) ;
2024-10-20 14:17:13 +02:00
}
2024-08-23 20:29:07 +02:00
} else {
storage . set ( index , v ) ;
}
} ) ;
} else {
storage . set ( index , v ) ;
}
} , token ) ;
if ( decl ! = nullptr ) {
+ + kt ;
}
}
2026-03-31 15:32:36 +02:00
return std : : make_shared < instance_data > ( owner , decl , ( decl & & decl - > as_entity ( ) ) ? name . value_or ( 0 ) : 0 , std : : move ( storage ) ) ;
2024-08-23 20:29:07 +02:00
}
2025-02-21 16:12:16 +01:00
2025-03-09 21:20:16 +01:00
/*
2026-03-31 15:32:36 +02:00
ifcopenshell::IfcBaseClass* ifcopenshell::impl::rocks_db_file_storage::rocksdb_instance_iterator::operator*() const {
2025-02-21 16:12:16 +01:00
auto it = storage_->byid_.find(*read_id_());
if (it != storage_->byid_.end()) {
// @todo define an implicit std::to_string() in all map adapters with leading 0s
auto jt = storage_->instance_cache_.find(it->second);
if (jt != storage_->instance_cache_.end()) {
return jt->second;
} else {
2025-02-27 22:07:31 +01:00
return storage_->assert_existance(it->first, by_name);
2025-02-21 16:12:16 +01:00
}
}
}
2025-03-09 21:20:16 +01:00
*/
2025-02-21 16:12:16 +01:00
2026-03-31 15:32:36 +02:00
ifcopenshell : : impl : : rocks_db_file_storage : : rocksdb_types_iterator : : value_type const & ifcopenshell : : impl : : rocks_db_file_storage : : rocksdb_types_iterator : : operator * ( ) const {
2025-02-21 16:12:16 +01:00
return storage_ - > file - > schema ( ) - > declarations ( ) [ * read_id_ ( ) ] ;
}
2026-03-31 15:32:36 +02:00
express : : Base ifcopenshell : : impl : : rocks_db_file_storage : : assert_existance ( size_t number , instance_ref r ) {
2025-09-03 11:11:55 +02:00
# ifdef IFOPSH_WITH_ROCKSDB
2026-03-31 15:32:36 +02:00
if ( r = = ifcopenshell : : impl : : rocks_db_file_storage : : entityinstance_ref ) {
2025-03-18 10:41:03 +01:00
auto it = instance_cache_ . find ( number ) ;
if ( it ! = instance_cache_ . end ( ) ) {
2026-01-04 10:40:02 +01:00
return express : : Base ( it - > second ) ;
2025-03-18 10:41:03 +01:00
}
} else {
auto it = type_instance_cache_ . find ( number ) ;
if ( it ! = type_instance_cache_ . end ( ) ) {
2026-01-04 10:40:02 +01:00
return express : : Base ( it - > second ) ;
2025-03-18 10:41:03 +01:00
}
2025-02-27 22:07:31 +01:00
}
2025-03-09 21:20:16 +01:00
std : : string v ;
2025-02-27 22:07:31 +01:00
2025-03-09 21:20:16 +01:00
rocksdb : : Status s = db - > Get ( rocksdb : : ReadOptions { } , ( r = = entityinstance_ref ? " i| " : " t| " ) + std : : to_string ( number ) + " |_ " , & v ) ;
2025-02-21 16:12:16 +01:00
if ( s . ok ( ) ) {
size_t s ;
memcpy ( & s , v . data ( ) , sizeof ( size_t ) ) ;
2025-02-27 22:07:31 +01:00
if ( s > = file - > schema ( ) - > declarations ( ) . size ( ) ) {
throw std : : runtime_error ( " " ) ;
}
2025-02-21 16:12:16 +01:00
auto decl = file - > schema ( ) - > declarations ( ) [ s ] ;
2025-02-27 22:07:31 +01:00
bool is_entity = decl - > as_entity ( ) ! = nullptr ;
2025-03-09 21:20:16 +01:00
if ( is_entity ! = ( r = = entityinstance_ref ) ) {
2025-02-27 22:07:31 +01:00
throw std : : runtime_error ( " Incorrect reference " ) ;
}
2026-03-31 15:32:36 +02:00
auto data = std : : make_shared < instance_data > ( file , decl , number , rocks_db_attribute_storage { } ) ;
if ( r = = ifcopenshell : : impl : : rocks_db_file_storage : : entityinstance_ref ) {
2026-01-04 10:40:02 +01:00
instance_cache_ . insert ( { number , data } ) ;
2025-03-18 10:41:03 +01:00
} else {
2026-01-04 10:40:02 +01:00
type_instance_cache_ . insert ( { number , data } ) ;
2025-03-18 10:41:03 +01:00
}
2026-01-04 10:40:02 +01:00
return express : : Base ( data ) ;
2025-03-13 12:34:54 +01:00
} else {
2026-03-31 15:32:36 +02:00
throw exception ( " Instance # " + boost : : lexical_cast < std : : string > ( number ) + " not found " ) ;
2025-02-21 16:12:16 +01:00
}
2025-08-26 13:56:03 +02:00
# else
2026-03-31 15:32:36 +02:00
throw exception ( " RocksDB support not compiled in " ) ;
2025-08-26 13:56:03 +02:00
# endif
2025-02-21 16:12:16 +01:00
}
2025-02-27 22:07:31 +01:00
namespace {
2025-09-03 11:11:55 +02:00
rocksdb : : DB * init_db ( const std : : string & filepath , bool readonly ) {
2025-08-26 13:56:03 +02:00
rocksdb : : DB * db = nullptr ;
2025-09-03 11:11:55 +02:00
# ifdef IFOPSH_WITH_ROCKSDB
2025-02-27 22:07:31 +01:00
rocksdb : : Options options ;
2025-03-14 16:16:25 +01:00
// options.disable_auto_compactions = true;
2025-02-27 22:07:31 +01:00
options . create_if_missing = true ;
2025-03-14 16:16:25 +01:00
options . merge_operator . reset ( new ConcatenateIdMergeOperator ( ) ) ;
2025-09-10 21:42:30 +02:00
auto vec = rocksdb : : GetSupportedCompressions ( ) ;
options . compression = std : : find ( vec . begin ( ) , vec . end ( ) , rocksdb : : kZSTD ) ! = vec . end ( ) ? rocksdb : : kZSTD : rocksdb : : kNoCompression ;
2025-09-03 11:11:55 +02:00
rocksdb : : BlockBasedTableOptions tbo ;
/*
tbo.block_size = 16 * 1024;
tbo.filter_policy.reset(rocksdb::NewBloomFilterPolicy(10 /*bits/key/, false));
tbo.partition_filters = true;
tbo.index_type = rocksdb::BlockBasedTableOptions::kHashSearch;
tbo.cache_index_and_filter_blocks = true;
tbo.cache_index_and_filter_blocks_with_high_priority = true;
tbo.pin_top_level_index_and_filter = true;
*/
2025-09-09 13:26:21 +02:00
// 28: 256MB
// 29: 512MB
// 30: 1GB
2025-09-03 11:11:55 +02:00
auto block_cache = rocksdb : : NewLRUCache ( 1ULL < < 30 ) ;
tbo . block_cache = block_cache ;
// rocksdb::CreateDBStatistics();
options . table_factory . reset ( rocksdb : : NewBlockBasedTableFactory ( tbo ) ) ;
rocksdb : : Status status ;
if ( readonly ) {
status = rocksdb : : DB : : OpenForReadOnly ( options , filepath , & db ) ;
} else {
status = rocksdb : : DB : : Open ( options , filepath , & db ) ;
}
2025-02-27 22:07:31 +01:00
if ( ! status . ok ( ) ) {
2025-09-03 11:11:55 +02:00
return nullptr ;
2025-02-27 22:07:31 +01:00
}
2025-09-03 11:11:55 +02:00
# endif // IFOPSH_WITH_ROCKSDB#
2025-02-27 22:07:31 +01:00
return db ;
}
}
2025-02-21 16:12:16 +01:00
// @todo naming
2026-03-31 15:32:36 +02:00
ifcopenshell : : impl : : rocks_db_file_storage : : rocks_db_file_storage ( const std : : string & filepath , ifcopenshell : : file * ffile , bool readonly )
2025-02-21 16:12:16 +01:00
: file ( ffile )
2025-09-03 11:11:55 +02:00
, db ( init_db ( filepath , readonly ) )
2025-09-11 19:10:05 +02:00
// @todo streaming serializer does not populate the byguid map
2026-01-04 10:40:02 +01:00
, byguid_internal_ ( db , " g| " ) ,
byguid_ ( & byguid_internal_ , [ this ] ( size_t v ) { return assert_existance ( v , entityinstance_ref ) ; } , [ ] ( const express : : Base & v ) { return v . identity ( ) ; } )
2025-03-09 21:20:16 +01:00
, instance_ids_ ( db , " i| " )
, instance_by_name_ ( & instance_ids_ , [ this ] ( size_t v ) { return assert_existance ( v , entityinstance_ref ) ; } )
2025-02-21 16:12:16 +01:00
, bytype_ ( db , " t| " )
2025-03-09 21:20:16 +01:00
, byref_excl_ ( db , " v| " )
2025-02-27 22:07:31 +01:00
// @todo by_identity is probably not correct here, this mapping is Name -> Identity, so Fn should have access to full pair?
2026-03-31 15:32:36 +02:00
// , byidentity_(&byid_, [this](size_t v) { return assert_existance(v, by_identity); }, [](ifcopenshell::IfcBaseClass* v) { return v->identity(); })
2025-03-14 16:16:25 +01:00
{
2025-09-14 13:53:20 +02:00
# ifdef IFOPSH_WITH_ROCKSDB
2025-09-11 19:10:05 +02:00
wopts . disableWAL = true ;
2025-09-14 13:53:20 +02:00
# endif
2025-03-14 16:16:25 +01:00
}
2025-02-27 22:07:31 +01:00
2026-03-31 15:32:36 +02:00
ifcopenshell : : impl : : rocks_db_file_storage : : ~ rocks_db_file_storage ( )
2025-02-21 16:12:16 +01:00
{
2025-09-03 11:11:55 +02:00
# ifdef IFOPSH_WITH_ROCKSDB
2025-02-27 22:07:31 +01:00
rocksdb : : FlushOptions flush_options ;
flush_options . allow_write_stall = true ;
flush_options . wait = true ; // Wait until flush completes.
rocksdb : : Status s = db - > Flush ( flush_options ) ;
2025-03-14 16:16:25 +01:00
// compact entire db
db - > CompactRange ( rocksdb : : CompactRangeOptions { } , nullptr , nullptr ) ;
2025-02-27 22:07:31 +01:00
assert ( s . ok ( ) ) ;
db - > Close ( ) ;
delete db ;
2025-08-26 13:56:03 +02:00
# endif
2025-02-21 16:12:16 +01:00
}
2025-02-27 22:07:31 +01:00
2026-03-31 15:32:36 +02:00
express : : Base ifcopenshell : : impl : : rocks_db_file_storage : : instance_by_id ( int id )
2025-02-21 16:12:16 +01:00
{
// @todo rename assert_existance() -> instance_by_id();
2025-03-09 21:20:16 +01:00
// - no cannot be done, because it needs to differentiate between entity instances and typedecls
return assert_existance ( id , entityinstance_ref ) ;
2025-02-21 16:12:16 +01:00
}
2026-03-31 15:32:36 +02:00
void ifcopenshell : : impl : : rocks_db_file_storage : : process_deletion_inverse ( const express : : Base & inst )
2025-02-24 20:46:29 +01:00
{
2025-09-03 11:11:55 +02:00
# ifdef IFOPSH_WITH_ROCKSDB
2026-01-04 10:40:02 +01:00
auto id = inst . id ( ) ;
2025-02-24 20:46:29 +01:00
{
2025-03-09 21:20:16 +01:00
// compute next prefix that does not start with v|{id}|
auto prefix = " v| " + std : : to_string ( id ) + " | " ;
2025-09-01 14:32:13 +02:00
auto it = std : : unique_ptr < rocksdb : : Iterator > ( db - > NewIterator ( rocksdb : : ReadOptions ( ) ) ) ;
2025-02-24 20:46:29 +01:00
it - > Seek ( prefix ) ;
while ( it - > Valid ( ) ) {
it - > Next ( ) ;
if ( ! it - > key ( ) . starts_with ( prefix ) ) {
break ;
}
}
rocksdb : : WriteBatch batch ;
batch . DeleteRange ( prefix , it - > key ( ) ) ;
2025-03-14 16:16:25 +01:00
db - > Write ( wopts , & batch ) ;
2025-02-24 20:46:29 +01:00
}
// This is based on traversal which needs instances to still be contained in the map.
// another option would be to keep byid intact for the remainder of this loop
2026-01-04 10:40:02 +01:00
auto entity_attributes = traverse ( inst , 1 ) ;
for ( auto & entity_attribute : entity_attributes ) {
2025-02-24 20:46:29 +01:00
if ( entity_attribute = = inst ) {
continue ;
}
2026-01-04 10:40:02 +01:00
const unsigned int name = entity_attribute . id ( ) ;
2025-02-24 20:46:29 +01:00
// Do not update inverses for simple types (which have id()==0 in IfcOpenShell).
if ( name ! = 0 ) {
// Find instances entity -> other
// and update inverses from entity into other
{
2025-03-09 21:20:16 +01:00
auto prefix = " v| " + std : : to_string ( name ) + " | " ;
2025-09-01 14:32:13 +02:00
auto it = std : : unique_ptr < rocksdb : : Iterator > ( db - > NewIterator ( rocksdb : : ReadOptions ( ) ) ) ;
2025-02-24 20:46:29 +01:00
it - > Seek ( prefix ) ;
while ( it - > Valid ( ) & & it - > key ( ) . starts_with ( prefix ) ) {
std : : string s = it - > value ( ) . ToString ( ) ;
// Iterator are snapshotted? So don't get invalidated?
std : : vector < size_t > vals ( s . size ( ) / sizeof ( size_t ) ) ;
memcpy ( vals . data ( ) , s . data ( ) , s . size ( ) ) ;
vals . erase ( std : : find ( vals . begin ( ) , vals . end ( ) , ( size_t ) id ) ) ;
s . resize ( vals . size ( ) * sizeof ( size_t ) ) ;
memcpy ( s . data ( ) , vals . data ( ) , s . size ( ) ) ;
2025-03-14 16:16:25 +01:00
db - > Put ( wopts , it - > key ( ) , s ) ;
2025-03-13 12:34:54 +01:00
it - > Next ( ) ;
2025-02-24 20:46:29 +01:00
}
}
}
}
2025-08-26 13:56:03 +02:00
# endif
2025-02-24 20:46:29 +01:00
}
2026-03-31 15:32:36 +02:00
express : : Base ifcopenshell : : impl : : in_memory_file_storage : : instance_by_id ( int id )
2025-02-21 16:12:16 +01:00
{
auto it = byid_ . find ( id ) ;
if ( it = = byid_ . end ( ) ) {
2026-03-31 15:32:36 +02:00
throw exception ( " Instance # " + boost : : lexical_cast < std : : string > ( id ) + " not found " ) ;
2025-02-21 16:12:16 +01:00
}
2026-01-04 10:40:02 +01:00
return express : : Base ( it - > second ) ;
2025-03-09 21:20:16 +01:00
}
2026-03-31 15:32:36 +02:00
ifcopenshell : : file : : ~ file ( ) { }
2025-03-13 13:09:39 +01:00
2025-09-29 11:29:07 +02:00
namespace {
// Utility functions for path handling in order not to rely on C++17's std::filesystem
# ifdef _WIN32
# define stat_t struct _stat
inline int stat_ ( const char * p , stat_t * s ) { return : : _stat ( p , s ) ; }
# ifndef S_ISDIR
# define S_ISDIR(m) (((m) & _S_IFDIR) != 0)
# endif
# ifndef S_ISREG
# define S_ISREG(m) (((m) & _S_IFREG) != 0)
# endif
# else
using stat_t = struct stat ;
inline int stat_ ( const char * p , stat_t * s ) { return : : stat ( p , s ) ; }
# endif
2025-03-13 13:09:39 +01:00
2025-09-29 11:29:07 +02:00
inline bool path_exists_ ( const std : : string & p , stat_t * out = nullptr ) {
stat_t tmp ;
stat_t * s = out ? out : & tmp ;
return stat_ ( p . c_str ( ) , s ) = = 0 ;
}
inline bool path_is_directory_ ( const stat_t & s ) { return S_ISDIR ( s . st_mode ) ; }
inline bool path_is_regular_file_ ( const stat_t & s ) { return S_ISREG ( s . st_mode ) ; }
inline std : : string path_join_ ( const std : : string & dir , const std : : string & name ) {
if ( dir . empty ( ) ) return name ;
const char last = dir . back ( ) ;
if ( last = = ' / ' | | last = = ' \\ ' ) return dir + name ;
# ifdef _WIN32
const char sep = ' \\ ' ;
# else
const char sep = ' / ' ;
# endif
return dir + sep + name ;
}
} // namespace
2025-03-13 13:09:39 +01:00
2026-03-31 15:32:36 +02:00
ifcopenshell : : filetype ifcopenshell : : guess_file_type ( const std : : string & fn ) {
2025-09-29 11:29:07 +02:00
stat_t st { } ;
if ( ! path_exists_ ( fn , & st ) ) {
2025-03-13 13:09:39 +01:00
// @todo this is just weird, but for consistency with earlier behaviour
// for now the only intent for this function is to auto-detect RocksDB
return FT_IFCSPF ;
}
2025-09-29 11:29:07 +02:00
if ( path_is_directory_ ( st ) ) {
2025-03-13 13:09:39 +01:00
// Typical RocksDB file to look for
2025-09-29 11:29:07 +02:00
auto currentFile = path_join_ ( fn , " CURRENT " ) ;
stat_t cst { } ;
2025-03-13 13:09:39 +01:00
2025-09-29 11:29:07 +02:00
if ( ! path_exists_ ( currentFile , & cst ) | | ! path_is_regular_file_ ( cst ) ) {
2025-03-13 13:09:39 +01:00
return FT_UNKNOWN ;
}
std : : ifstream infile ( currentFile ) ;
if ( ! infile ) {
return FT_UNKNOWN ;
}
std : : string line ;
if ( ! std : : getline ( infile , line ) ) {
return FT_UNKNOWN ;
}
// RocksDB's CURRENT file typically contains a line like "MANIFEST-000001".
if ( line . find ( " MANIFEST- " ) = = 0 ) {
return FT_ROCKSDB ;
}
2025-08-25 12:45:10 +02:00
return FT_UNKNOWN ;
2025-03-13 13:09:39 +01:00
} else {
// @todo just return SPF for now, but ideally this will be augmented with all other options
return FT_IFCSPF ;
}
}
2025-08-25 12:45:10 +02:00
2026-03-31 15:32:36 +02:00
express : : Base ifcopenshell : : impl : : rocks_db_file_storage : : create ( const ifcopenshell : : declaration * decl , int id ) {
2026-01-04 10:40:02 +01:00
return express : : Base { } ;
/*
2025-08-27 10:32:58 +02:00
if (decl->as_entity() || decl->as_type_declaration()) {
auto* inst = file->schema()->instantiate(decl, rocks_db_attribute_storage{});
2025-10-20 10:37:35 +02:00
// @todo maybe this needs to be set to file? In order to have a context (ie. rocksdb::db*) to write to?
2025-10-20 10:33:09 +02:00
inst->file_ = nullptr;
2026-03-31 15:32:36 +02:00
return file->add_entity(inst);
2025-08-27 10:32:58 +02:00
} else {
throw std::runtime_error("Requires and entity or type declaration");
}
2026-01-04 10:40:02 +01:00
*/
2025-08-27 10:32:58 +02:00
}
2026-03-31 15:32:36 +02:00
express : : Base ifcopenshell : : impl : : in_memory_file_storage : : create ( const ifcopenshell : : declaration * decl , int id ) {
2026-01-08 11:49:29 +01:00
uint32_t instance_name ;
if ( decl - > as_entity ( ) ! = nullptr ) {
2026-03-31 15:32:36 +02:00
instance_name = id = = - 1 ? ( int ) file - > fresh_id ( ) : id ;
2026-01-08 11:49:29 +01:00
} else if ( decl - > as_type_declaration ( ) ! = nullptr ) {
instance_name = 0 ;
} else {
2025-08-27 10:32:58 +02:00
throw std : : runtime_error ( " Requires and entity or type declaration " ) ;
}
2026-03-31 15:32:36 +02:00
auto data = std : : make_shared < instance_data > ( file , decl , instance_name , decl - > as_entity ( ) ? in_memory_attribute_storage ( decl - > as_entity ( ) - > attribute_count ( ) ) : in_memory_attribute_storage ( 1 ) ) ;
2026-01-13 08:44:44 +01:00
if ( instance_name ) {
byid_ . insert ( { instance_name , data } ) ;
} else {
tbyid_ . insert ( { data - > identity ( ) , data } ) ;
}
express : : Base inst ( data ) ;
2026-01-07 13:51:48 +01:00
add_type_ref ( inst ) ;
2026-01-04 10:40:02 +01:00
return inst ;
}
2026-03-31 15:32:36 +02:00
express : : Base ifcopenshell : : file : : create ( const ifcopenshell : : declaration * decl , int id ) {
2026-01-04 10:40:02 +01:00
if ( id ! = - 1 ) {
2026-01-14 14:00:00 +01:00
if ( decl - > as_entity ( ) = = nullptr ) {
2026-03-31 15:32:36 +02:00
throw ifcopenshell : : exception ( " Assigning instance id during creation is only valid for entity declarations " ) ;
2026-01-14 14:00:00 +01:00
}
2026-01-04 10:40:02 +01:00
bool id_already_exists = false ;
try {
if ( check_existance_before_adding ) {
instance_by_id ( id ) ;
id_already_exists = true ;
}
} catch ( . . . ) {
}
if ( id_already_exists ) {
2026-03-31 15:32:36 +02:00
throw ifcopenshell : : exception ( " An instance with id " + boost : : lexical_cast < std : : string > ( id ) + " is already part of this file " ) ;
2026-01-04 10:40:02 +01:00
}
2026-01-14 14:00:00 +01:00
if ( ( unsigned ) id > max_id_ ) {
max_id_ = ( unsigned ) id ;
}
2026-01-04 10:40:02 +01:00
}
return std : : visit ( [ & ] ( auto & m ) - > express : : Base {
if constexpr ( std : : is_same_v < std : : decay_t < decltype ( m ) > , impl : : in_memory_file_storage > | |
std : : is_same_v < std : : decay_t < decltype ( m ) > , impl : : rocks_db_file_storage > ) {
return m . create ( decl , id ) ;
} else {
return express : : Base { } ;
}
} , storage_ ) ;
2025-08-27 10:32:58 +02:00
}
2026-01-04 10:40:02 +01:00