NAME

SQL::SyntaxModel::SkipID - Use SQL::SyntaxModels without inputting Node IDs

DEPENDENCIES

Perl Version: 5.006

Standard Modules: none

Nonstandard Modules:

Locale::KeyedText 0.03 (for error messages)
SQL::SyntaxModel 0.15 (parent class)

COPYRIGHT AND LICENSE

This module is Copyright (c) 1999-2004, Darren R. Duncan. All rights reserved. Address comments, suggestions, and bug reports to perl@DarrenDuncan.net, or visit "http://www.DarrenDuncan.net" for more information.

This module is free software; you can redistribute it and/or modify it under the same terms as Perl 5.8 itself.

Any versions of this module that you modify and distribute must carry prominent notices stating that you changed the files and the date of any changes, in addition to preserving this original copyright notice and other credits. This module is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

SYNOPSIS

See the CONTRIVED EXAMPLE documentation section at the end.

DESCRIPTION

The SQL::SyntaxModel::SkipID Perl 5 module is a completely optional extension to SQL::SyntaxModel, and is implemented as a sub-class of that module. This module implements two distinct sets of additional features.

The First Set of Additional Features

This module adds a set of 4 new public methods which you can use to make some tasks involving SQL::SyntaxModel less labour-intensive, depending on how you like to use the module.

Using them, you can create a Node, set all of its attributes, put it in a Container, and likewise recursively create all of its child Nodes, all with a single method call. In the context of this module, the set of Nodes consisting of one starting Node and all of its "descendants" is called a "tree". You can create a tree of Nodes in mainly two contexts; one context will assign the starting Node of the new tree as a child of an already existing Node; the other will not explicitly attach the tree to an existing Node.

All of the added methods are wrappers over existing parent class methods, and this module does not define any new class properties that are used by them.

The Second Set of Additional Features

The public interface to this module is essentially the same as its parent, with the difference being that SQL::SyntaxModel::SkipID will accept a wider variety of input data formats into its methods. Therefore, this module's documentation does not list or explain its methods (see the parent class for that), but it will mention any differences from the parent.

The extension is intended to be fully parent-compatible, meaning that if you provide it input which would be acceptable to the stricter bare parent class, then you will get the same behaviour. Where you will see the difference is when you provide certain kinds of input which would cause the parent class to return an error and/or throw an exception.

One significant added feature, which is part of this module's name-sake, is that it will automatically generate (by serial number) a new Node's "id" attribute when your input doesn't provide one. A related name-sake feature is that, when you want to refer to an earlier created Node by a later one, for purposes of linking them, you can refer to the earlier Node by a more human-readable attribute than the Node's "id" (or Node ref), such as its 'name' (which is also what actual SQL uses). Between these two name-sake features, it is possible to use SQL::SyntaxModel::SkipID without ever having to explicitly see a Node's "id" attribute.

Note that, for the sake of avoiding conflicts, you should not be explicitly setting ids for some Nodes of a type, and having others auto-generated, unless you take extra precautions. This is because while auto-generated Node ids will not conflict with prior explicit ones, later provided explicit ones may conflict with auto-generated ones. How you can resolve this is to use the parent class' get_node() method to see if the id you want is already in use. The same caveats apply as if the auto-generator was a second concurrent user editing the object. This said, you can mix references from one Node to another between id and non-id ref types without further consequence, because they don't change the id of a Node.

Another added feature is that this class can automatically assign a parent Node for a newly created Node that doesn't explicitly specify a parent in some way, such as in a create_node() argument or by the fact you are calling add_child_node(). This automatic assignment is context-sensitive, whereby the most recent previously-created Node which is capable of becoming the new one's parent will do so.

This module's added features can make it "easier to use" in some circumstances than the bare-bones SQL::SyntaxModel::SkipID, including an appearance more like actual SQL strings, because matching descriptive terms can be used in multiple places.

However, the functionality has its added cost in code complexity and reliability; for example, since non-id attributes are not unique, the module can "guess wrong" about what you wanted to do, and it won't work at all in some circumstances. Additionally, since your code, by using this module, would use descriptive attributes to link Nodes together, you will have to update every place you use the attribute value when you change the original, so they continue to match; this is unlike the bare parent class, which always uses non-descriptive attributes for links, which you are unlikely to ever change. The added logic also makes the code slower and use more memory.

CONTAINER OBJECT METHODS

create_node_tree( { NODE_TYPE[, ATTRS][, CHILDREN] } )

my $node = $model->create_node_tree( 
	{ 'NODE_TYPE' => 'catalog', 'ATTRS' => { 'id' => 1, } } ); 

This "setter" method creates a new Node object within the context of the current Container and returns it. It takes a hash ref containing up to 3 named arguments: NODE_TYPE, ATTRS, CHILDREN. The first argument, NODE_TYPE, is a string (enum) which specifies the Node Type of the new Node. The second (optional) argument, ATTRS, is a hash ref whose elements will go in the various "attributes" properties of the new Node (and the "node id" property if applicable). Any attributes which will refer to another Node can be passed in as either a Node object reference or an integer which matches the 'id' attribute of an already created Node. The third (optional) argument, CHILDREN, is an array ref whose elements will also be recursively made into new Nodes, for which their primary parent is the Node you have just made here. Elements in CHILDREN are always processed after the other arguments. If the root Node you are about to make should have a primary parent Node, then you would be better to use said parent's create_child_node_tree[/s] method instead of this one. This method is actually a "wrapper" for a set of other, simpler function/method calls that you could call directly instead if you wanted more control over the process.

create_node_trees( LIST )

$model->create_nodes( [{ ... }, { ... }] );
$model->create_nodes( { ... } );

This "setter" method takes an array ref in its single LIST argument, and calls create_node_tree() for each element found in it.

NODE OBJECT METHODS

create_child_node_tree( { NODE_TYPE[, ATTRS][, CHILDREN] } )

my $new_child = $node->add_child_node( 
	{ 'NODE_TYPE' => 'schema', 'ATTRS' => { 'id' => 1, } } ); 

This "setter" method will create a new Node, following the same semantics (and taking the same arguments) as the Container->create_node_tree(), except that create_child_node_tree() will also set the primary parent of the new Node to be the current Node. This method also returns the new child Node.

create_child_node_trees( LIST )

$model->create_child_node_tree( [$child1,$child2] );
$model->create_child_node_tree( $child );

This "setter" method takes an array ref in its single LIST argument, and calls create_child_node_tree() for each element found in it.

BUGS

See the BUGS main documentation section of SQL::SyntaxModel since everything said there applies to this module also.

The "use base ..." pragma doesn't seem to work properly (with Perl 5.6 at least) when I want to inherit from multiple classes, with some required parent class methods not being seen; I had to use the analagous "use vars @ISA; @ISA = ..." syntax instead.

The mechanisms for automatically linking nodes to each other, and particularly for resolving parent-child node relationships, are under-developed (somewhat hackish) at the moment and probably won't work properly in all situations. However, they do work for the CONTRIVED EXAMPLE code. This linking code may gradually be improved if there is a need.

CAVEATS

See the CAVEATS main documentation section of SQL::SyntaxModel since everything said there applies to this module also.

See the TODO file for an important message concerning the future of this module.

SEE ALSO

SQL::SyntaxModel::SkipID::L::*, SQL::SyntaxModel, and other items in its SEE ALSO documentation; also SQL::SyntaxModel::ByTree.

CONTRIVED EXAMPLE

The following demonstrates input that can be provided to SQL::SyntaxModel::SkipID, along with a way to debug the result; it is a contrived example since the class normally wouldn't get used this way. This code is exactly the same (except for framing) as that run by this module's current test script.

use strict;
use warnings;

use SQL::SyntaxModel::SkipID;

my $model = SQL::SyntaxModel::SkipID->new_container();

$model->create_node_trees( [ map { { 'NODE_TYPE' => 'domain', 'ATTRS' => $_ } } (
	{ 'name' => 'bin1k' , 'base_type' => 'STR_BIT', 'max_octets' =>  1_000, },
	{ 'name' => 'bin32k', 'base_type' => 'STR_BIT', 'max_octets' => 32_000, },
	{ 'name' => 'str4'  , 'base_type' => 'STR_CHAR', 'max_chars' =>  4, 'store_fixed' => 1, 
		'char_enc' => 'ASCII', 'trim_white' => 1, 'uc_latin' => 1, 
		'pad_char' => ' ', 'trim_pad' => 1, },
	{ 'name' => 'str10' , 'base_type' => 'STR_CHAR', 'max_chars' => 10, 'store_fixed' => 1, 
		'char_enc' => 'ASCII', 'trim_white' => 1, 
		'pad_char' => ' ', 'trim_pad' => 1, },
	{ 'name' => 'str30' , 'base_type' => 'STR_CHAR', 'max_chars' =>    30, 
		'char_enc' => 'ASCII', 'trim_white' => 1, },
	{ 'name' => 'str2k' , 'base_type' => 'STR_CHAR', 'max_chars' => 2_000, 'char_enc' => 'UTF8', },
	{ 'name' => 'byte' , 'base_type' => 'NUM_INT', 'num_scale' =>  3, },
	{ 'name' => 'short', 'base_type' => 'NUM_INT', 'num_scale' =>  5, },
	{ 'name' => 'int'  , 'base_type' => 'NUM_INT', 'num_scale' => 10, },
	{ 'name' => 'long' , 'base_type' => 'NUM_INT', 'num_scale' => 19, },
	{ 'name' => 'ubyte' , 'base_type' => 'NUM_INT', 'num_scale' =>  3, 'num_unsigned' => 1, },
	{ 'name' => 'ushort', 'base_type' => 'NUM_INT', 'num_scale' =>  5, 'num_unsigned' => 1, },
	{ 'name' => 'uint'  , 'base_type' => 'NUM_INT', 'num_scale' => 10, 'num_unsigned' => 1, },
	{ 'name' => 'ulong' , 'base_type' => 'NUM_INT', 'num_scale' => 19, 'num_unsigned' => 1, },
	{ 'name' => 'float' , 'base_type' => 'NUM_APR', 'num_octets' => 4, },
	{ 'name' => 'double', 'base_type' => 'NUM_APR', 'num_octets' => 8, },
	{ 'name' => 'dec10p2', 'base_type' => 'NUM_EXA', 'num_scale' =>  10, 'num_precision' => 2, },
	{ 'name' => 'dec255' , 'base_type' => 'NUM_EXA', 'num_scale' => 255, },
	{ 'name' => 'boolean', 'base_type' => 'BOOLEAN', },
	{ 'name' => 'datetime', 'base_type' => 'DATETIME', 'calendar' => 'ABS', },
	{ 'name' => 'dtchines', 'base_type' => 'DATETIME', 'calendar' => 'CHI', },
	{ 'name' => 'sex'   , 'base_type' => 'STR_CHAR', 'max_chars' =>     1, },
	{ 'name' => 'str20' , 'base_type' => 'STR_CHAR', 'max_chars' =>    20, },
	{ 'name' => 'str100', 'base_type' => 'STR_CHAR', 'max_chars' =>   100, },
	{ 'name' => 'str250', 'base_type' => 'STR_CHAR', 'max_chars' =>   250, },
	{ 'name' => 'entitynm', 'base_type' => 'STR_CHAR', 'max_chars' =>  30, },
	{ 'name' => 'generic' , 'base_type' => 'STR_CHAR', 'max_chars' => 250, },
) ] );

$model->create_node_trees( ['catalog', 'owner', 'schema'] );

$model->create_node_tree( { 'NODE_TYPE' => 'table', 
		'ATTRS' => { 'name' => 'person', }, 'CHILDREN' => [ 
	( map { { 'NODE_TYPE' => 'table_col', 'ATTRS' => $_ } } (
		{
			'name' => 'person_id', 'domain' => 'int', 'mandatory' => 1,
			'default_val' => 1, 'auto_inc' => 1,
		},
		{ 'name' => 'alternate_id', 'domain' => 'str20' , },
		{ 'name' => 'name'        , 'domain' => 'str100', 'mandatory' => 1, },
		{ 'name' => 'sex'         , 'domain' => 'sex'   , },
		{ 'name' => 'father_id'   , 'domain' => 'int'   , },
		{ 'name' => 'mother_id'   , 'domain' => 'int'   , },
	) ),
	( map { { 'NODE_TYPE' => 'table_ind', 'ATTRS' => $_->[0], 
			'CHILDREN' => { 'NODE_TYPE' => 'table_ind_col', 'ATTRS' => $_->[1] } } } (
		[ { 'name' => 'primary'        , 'ind_type' => 'UNIQUE', }, 'person_id'    ], 
		[ { 'name' => 'ak_alternate_id', 'ind_type' => 'UNIQUE', }, 'alternate_id' ], 
		[ { 'name' => 'fk_father', 'ind_type' => 'FOREIGN', 'f_table' => 'person', }, 
			{ 'table_col' => 'father_id', 'f_table_col' => 'person_id' } ], 
		[ { 'name' => 'fk_mother', 'ind_type' => 'FOREIGN', 'f_table' => 'person', }, 
			{ 'table_col' => 'mother_id', 'f_table_col' => 'person_id' } ], 
	) ),
] } );

$model->create_node_tree( { 'NODE_TYPE' => 'view', 
		'ATTRS' => { 'name' => 'person', 'view_type' => 'MATCH', 'may_write' => 1 }, 'CHILDREN' => [ 
	{ 'NODE_TYPE' => 'view_src', 'ATTRS' => { 'name' => 'person', 'match_table' => 'person', }, },
] } );

$model->create_node_tree( { 'NODE_TYPE' => 'view', 
		'ATTRS' => { 'name' => 'person_with_parents', 'may_write' => 0, }, 'CHILDREN' => [ 
	( map { { 'NODE_TYPE' => 'view_col', 'ATTRS' => $_ } } (
		{ 'name' => 'self_id'    , 'domain' => 'int'   , },
		{ 'name' => 'self_name'  , 'domain' => 'str100', },
		{ 'name' => 'father_id'  , 'domain' => 'int'   , },
		{ 'name' => 'father_name', 'domain' => 'str100', },
		{ 'name' => 'mother_id'  , 'domain' => 'int'   , },
		{ 'name' => 'mother_name', 'domain' => 'str100', },
	) ),
	( map { { 'NODE_TYPE' => 'view_src', 'ATTRS' => { 'name' => $_, 'match_table' => 'person', }, 
		'CHILDREN' => [ map { { 'NODE_TYPE' => 'view_src_col', 'ATTRS' => $_ } } qw( person_id name father_id mother_id ) ] 
	} } qw( self ) ),
	( map { { 'NODE_TYPE' => 'view_src', 'ATTRS' => { 'name' => $_, 'match_table' => 'person', }, 
		'CHILDREN' => [ map { { 'NODE_TYPE' => 'view_src_col', 'ATTRS' => $_ } } qw( person_id name ) ] 
	} } qw( father mother ) ),
	{ 'NODE_TYPE' => 'view_join', 'ATTRS' => { 'lhs_src' => 'self', 
			'rhs_src' => 'father', 'join_type' => 'LEFT', }, 'CHILDREN' => [ 
		{ 'NODE_TYPE' => 'view_join_col', 'ATTRS' => { 'lhs_src_col' => 'father_id', 
			'rhs_src_col' => 'person_id',  } },
	] },
	{ 'NODE_TYPE' => 'view_join', 'ATTRS' => { 'lhs_src' => 'self', 
			'rhs_src' => 'mother', 'join_type' => 'LEFT', }, 'CHILDREN' => [ 
		{ 'NODE_TYPE' => 'view_join_col', 'ATTRS' => { 'lhs_src_col' => 'mother_id', 
			'rhs_src_col' => 'person_id',  } },
	] },
	( map { { 'NODE_TYPE' => 'view_expr', 'ATTRS' => $_ } } (
		{ 'view_part' => 'RESULT', 'view_col' => 'self_id'    , 'expr_type' => 'COL', 'src_col' => ['person_id','self'], },
		{ 'view_part' => 'RESULT', 'view_col' => 'self_name'  , 'expr_type' => 'COL', 'src_col' => ['name'     ,'self'], },
		{ 'view_part' => 'RESULT', 'view_col' => 'father_id'  , 'expr_type' => 'COL', 'src_col' => ['person_id','father'], },
		{ 'view_part' => 'RESULT', 'view_col' => 'father_name', 'expr_type' => 'COL', 'src_col' => ['name'     ,'father'], },
		{ 'view_part' => 'RESULT', 'view_col' => 'mother_id'  , 'expr_type' => 'COL', 'src_col' => ['person_id','mother'], },
		{ 'view_part' => 'RESULT', 'view_col' => 'mother_name', 'expr_type' => 'COL', 'src_col' => ['name'     ,'mother'], },
	) ),
	{ 'NODE_TYPE' => 'view_expr', 'ATTRS' => { 'view_part' => 'WHERE', 
			'expr_type' => 'SFUNC', 'sfunc' => 'AND', }, 'CHILDREN' => [ 
		{ 'NODE_TYPE' => 'view_expr', 'ATTRS' => { 
				'expr_type' => 'SFUNC', 'sfunc' => 'LIKE', }, 'CHILDREN' => [ 
			{ 'NODE_TYPE' => 'view_expr', 'ATTRS' => { 
				'expr_type' => 'COL', 'src_col' => ['name','father'], }, },
			{ 'NODE_TYPE' => 'view_expr', 'ATTRS' => { 
				'expr_type' => 'VAR', }, }, #'routine_var' => 'srchw_fa',
		] },
		{ 'NODE_TYPE' => 'view_expr', 'ATTRS' => { 
				'expr_type' => 'SFUNC', 'sfunc' => 'LIKE', }, 'CHILDREN' => [ 
			{ 'NODE_TYPE' => 'view_expr', 'ATTRS' => { 
				'expr_type' => 'COL', 'src_col' => ['name','mother'], }, },
			{ 'NODE_TYPE' => 'view_expr', 'ATTRS' => { 
				'expr_type' => 'VAR', }, }, #'routine_var' => 'srchw_mo',
		] },
	] },
] } );

$model->create_node_tree( { 'NODE_TYPE' => 'table', 
		'ATTRS' => { 'name' => 'user_auth', }, 'CHILDREN' => [ 
	( map { { 'NODE_TYPE' => 'table_col', 'ATTRS' => $_ } } (
		{
			'name' => 'user_id', 'domain' => 'int', 'mandatory' => 1,
			'default_val' => 1, 'auto_inc' => 1,
		},
		{ 'name' => 'login_name'   , 'domain' => 'str20'  , 'mandatory' => 1, },
		{ 'name' => 'login_pass'   , 'domain' => 'str20'  , 'mandatory' => 1, },
		{ 'name' => 'private_name' , 'domain' => 'str100' , 'mandatory' => 1, },
		{ 'name' => 'private_email', 'domain' => 'str100' , 'mandatory' => 1, },
		{ 'name' => 'may_login'    , 'domain' => 'boolean', 'mandatory' => 1, },
		{ 
			'name' => 'max_sessions', 'domain' => 'byte', 'mandatory' => 1, 
			'default_val' => 3, 
		},
	) ),
	( map { { 'NODE_TYPE' => 'table_ind', 'ATTRS' => $_->[0], 
			'CHILDREN' => { 'NODE_TYPE' => 'table_ind_col', 'ATTRS' => $_->[1] } } } (
		[ { 'name' => 'primary'         , 'ind_type' => 'UNIQUE', }, 'user_id'       ],
		[ { 'name' => 'ak_login_name'   , 'ind_type' => 'UNIQUE', }, 'login_name'    ],
		[ { 'name' => 'ak_private_email', 'ind_type' => 'UNIQUE', }, 'private_email' ],
	) ),
] } );

$model->create_node_tree( { 'NODE_TYPE' => 'table', 
		'ATTRS' => { 'name' => 'user_profile', }, 'CHILDREN' => [ 
	( map { { 'NODE_TYPE' => 'table_col', 'ATTRS' => $_ } } (
		{ 'name' => 'user_id'     , 'domain' => 'int'   , 'mandatory' => 1, },
		{ 'name' => 'public_name' , 'domain' => 'str250', 'mandatory' => 1, },
		{ 'name' => 'public_email', 'domain' => 'str250', 'mandatory' => 0, },
		{ 'name' => 'web_url'     , 'domain' => 'str250', 'mandatory' => 0, },
		{ 'name' => 'contact_net' , 'domain' => 'str250', 'mandatory' => 0, },
		{ 'name' => 'contact_phy' , 'domain' => 'str250', 'mandatory' => 0, },
		{ 'name' => 'bio'         , 'domain' => 'str250', 'mandatory' => 0, },
		{ 'name' => 'plan'        , 'domain' => 'str250', 'mandatory' => 0, },
		{ 'name' => 'comments'    , 'domain' => 'str250', 'mandatory' => 0, },
	) ),
	( map { { 'NODE_TYPE' => 'table_ind', 'ATTRS' => $_->[0], 
			'CHILDREN' => { 'NODE_TYPE' => 'table_ind_col', 'ATTRS' => $_->[1] } } } (
		[ { 'name' => 'primary'       , 'ind_type' => 'UNIQUE', }, 'user_id'     ],
		[ { 'name' => 'ak_public_name', 'ind_type' => 'UNIQUE', }, 'public_name' ],
		[ { 'name' => 'fk_user', 'ind_type' => 'FOREIGN', 'f_table' => 'user_auth', }, 
			{ 'table_col' => 'user_id', 'f_table_col' => 'user_id' } ], 
	) ),
] } );

$model->create_node_tree( { 'NODE_TYPE' => 'view', 
		'ATTRS' => { 'name' => 'user', 'may_write' => 1, }, 'CHILDREN' => [ 
	( map { { 'NODE_TYPE' => 'view_col', 'ATTRS' => $_ } } (
		{ 'name' => 'user_id'      , 'domain' => 'int'    , },
		{ 'name' => 'login_name'   , 'domain' => 'str20'  , },
		{ 'name' => 'login_pass'   , 'domain' => 'str20'  , },
		{ 'name' => 'private_name' , 'domain' => 'str100' , },
		{ 'name' => 'private_email', 'domain' => 'str100' , },
		{ 'name' => 'may_login'    , 'domain' => 'boolean', },
		{ 'name' => 'max_sessions' , 'domain' => 'byte'   , },
		{ 'name' => 'public_name'  , 'domain' => 'str250' , },
		{ 'name' => 'public_email' , 'domain' => 'str250' , },
		{ 'name' => 'web_url'      , 'domain' => 'str250' , },
		{ 'name' => 'contact_net'  , 'domain' => 'str250' , },
		{ 'name' => 'contact_phy'  , 'domain' => 'str250' , },
		{ 'name' => 'bio'          , 'domain' => 'str250' , },
		{ 'name' => 'plan'         , 'domain' => 'str250' , },
		{ 'name' => 'comments'     , 'domain' => 'str250' , },
	) ),
	{ 'NODE_TYPE' => 'view_src', 'ATTRS' => { 'name' => 'user_auth', 
			'match_table' => 'user_auth', }, 'CHILDREN' => [ 
		( map { { 'NODE_TYPE' => 'view_src_col', 'ATTRS' => $_ } } qw(
			user_id login_name login_pass private_name private_email may_login max_sessions
		) ),
	] },
	{ 'NODE_TYPE' => 'view_src', 'ATTRS' => { 'name' => 'user_profile', 
			'match_table' => 'user_profile', }, 'CHILDREN' => [ 
		( map { { 'NODE_TYPE' => 'view_src_col', 'ATTRS' => $_ } } qw(
			user_id public_name public_email web_url contact_net contact_phy bio plan comments
		) ),
	] },
	{ 'NODE_TYPE' => 'view_join', 'ATTRS' => { 'lhs_src' => 'user_auth', 
			'rhs_src' => 'user_profile', 'join_type' => 'LEFT', }, 'CHILDREN' => [ 
		{ 'NODE_TYPE' => 'view_join_col', 'ATTRS' => { 'lhs_src_col' => 'user_id', 
			'rhs_src_col' => 'user_id',  } },
	] },
	( map { { 'NODE_TYPE' => 'view_expr', 'ATTRS' => $_ } } (
		{ 'view_part' => 'RESULT', 'view_col' => 'user_id'      , 'expr_type' => 'COL', 'src_col' => ['user_id'      ,'user_auth'   ], },
		{ 'view_part' => 'RESULT', 'view_col' => 'login_name'   , 'expr_type' => 'COL', 'src_col' => ['login_name'   ,'user_auth'   ], },
		{ 'view_part' => 'RESULT', 'view_col' => 'login_pass'   , 'expr_type' => 'COL', 'src_col' => ['login_pass'   ,'user_auth'   ], },
		{ 'view_part' => 'RESULT', 'view_col' => 'private_name' , 'expr_type' => 'COL', 'src_col' => ['private_name' ,'user_auth'   ], },
		{ 'view_part' => 'RESULT', 'view_col' => 'private_email', 'expr_type' => 'COL', 'src_col' => ['private_email','user_auth'   ], },
		{ 'view_part' => 'RESULT', 'view_col' => 'may_login'    , 'expr_type' => 'COL', 'src_col' => ['may_login'    ,'user_auth'   ], },
		{ 'view_part' => 'RESULT', 'view_col' => 'max_sessions' , 'expr_type' => 'COL', 'src_col' => ['max_sessions' ,'user_auth'   ], },
		{ 'view_part' => 'RESULT', 'view_col' => 'public_name'  , 'expr_type' => 'COL', 'src_col' => ['public_name'  ,'user_profile'], },
		{ 'view_part' => 'RESULT', 'view_col' => 'public_email' , 'expr_type' => 'COL', 'src_col' => ['public_email' ,'user_profile'], },
		{ 'view_part' => 'RESULT', 'view_col' => 'web_url'      , 'expr_type' => 'COL', 'src_col' => ['web_url'      ,'user_profile'], },
		{ 'view_part' => 'RESULT', 'view_col' => 'contact_net'  , 'expr_type' => 'COL', 'src_col' => ['contact_net'  ,'user_profile'], },
		{ 'view_part' => 'RESULT', 'view_col' => 'contact_phy'  , 'expr_type' => 'COL', 'src_col' => ['contact_phy'  ,'user_profile'], },
		{ 'view_part' => 'RESULT', 'view_col' => 'bio'          , 'expr_type' => 'COL', 'src_col' => ['bio'          ,'user_profile'], },
		{ 'view_part' => 'RESULT', 'view_col' => 'plan'         , 'expr_type' => 'COL', 'src_col' => ['plan'         ,'user_profile'], },
		{ 'view_part' => 'RESULT', 'view_col' => 'comments'     , 'expr_type' => 'COL', 'src_col' => ['comments'     ,'user_profile'], },
	) ),
	{ 'NODE_TYPE' => 'view_expr', 'ATTRS' => { 'view_part' => 'WHERE', 
			'expr_type' => 'SFUNC', 'sfunc' => 'EQ', }, 'CHILDREN' => [ 
		{ 'NODE_TYPE' => 'view_expr', 'ATTRS' => { 
			'expr_type' => 'COL', 'src_col' => ['user_id','user_auth'], }, },
		{ 'NODE_TYPE' => 'view_expr', 'ATTRS' => { 
			'expr_type' => 'VAR', }, }, #'routine_var' => 'curr_uid',
	] },
] } );

$model->create_node_tree( { 'NODE_TYPE' => 'table', 
		'ATTRS' => { 'name' => 'user_pref', }, 'CHILDREN' => [ 
	( map { { 'NODE_TYPE' => 'table_col', 'ATTRS' => $_ } } (
		{ 'name' => 'user_id'   , 'domain' => 'int'     , 'mandatory' => 1, },
		{ 'name' => 'pref_name' , 'domain' => 'entitynm', 'mandatory' => 1, },
		{ 'name' => 'pref_value', 'domain' => 'generic' , 'mandatory' => 0, },
	) ),
	( map { { 'NODE_TYPE' => 'table_ind', 'ATTRS' => $_->[0], 'CHILDREN' => [ 
			map { { 'NODE_TYPE' => 'table_ind_col', 'ATTRS' => $_ } } @{$_->[1]}
			] } } (
		[ { 'name' => 'primary', 'ind_type' => 'UNIQUE', }, [ 'user_id', 'pref_name', ], ], 
		[ { 'name' => 'fk_user', 'ind_type' => 'FOREIGN', 'f_table' => 'user_auth', }, 
			[ { 'table_col' => 'user_id', 'f_table_col' => 'user_id' }, ], ], 
	) ),
] } );

$model->create_node_tree( { 'NODE_TYPE' => 'view', 
		'ATTRS' => { 'name' => 'user_theme', 'view_type' => 'SINGLE', 'may_write' => 0, }, 'CHILDREN' => [ 
	( map { { 'NODE_TYPE' => 'view_col', 'ATTRS' => $_ } } (
		{ 'name' => 'theme_name' , 'domain' => 'generic', },
		{ 'name' => 'theme_count', 'domain' => 'int'    , },
	) ),
	{ 'NODE_TYPE' => 'view_src', 'ATTRS' => { 'name' => 'user_pref', 'match_table' => 'user_pref', }, 
		'CHILDREN' => [ map { { 'NODE_TYPE' => 'view_src_col', 'ATTRS' => $_ } } qw( pref_name pref_value ) ] 
	},
	{ 'NODE_TYPE' => 'view_expr', 'ATTRS' => { 'view_part' => 'RESULT', 
		'view_col' => 'theme_name', 'expr_type' => 'COL', 'src_col' => ['pref_value','user_pref'], }, },
	{ 'NODE_TYPE' => 'view_expr', 'ATTRS' => { 'view_part' => 'RESULT', 
			'view_col' => 'theme_count', 'expr_type' => 'SFUNC', 'sfunc' => 'GCOUNT', }, 'CHILDREN' => [ 
		{ 'NODE_TYPE' => 'view_expr', 'ATTRS' => { 
			'expr_type' => 'COL', 'src_col' => ['pref_value','user_pref'], }, },
	] },
	{ 'NODE_TYPE' => 'view_expr', 'ATTRS' => { 'view_part' => 'WHERE', 
			'expr_type' => 'SFUNC', 'sfunc' => 'EQ', }, 'CHILDREN' => [ 
		{ 'NODE_TYPE' => 'view_expr', 'ATTRS' => { 
			'expr_type' => 'COL', 'src_col' => ['pref_name','user_pref'], }, },
		{ 'NODE_TYPE' => 'view_expr', 'ATTRS' => { 
			'expr_type' => 'LIT', 'lit_val' => 'theme', }, },
	] },
	{ 'NODE_TYPE' => 'view_expr', 'ATTRS' => { 'view_part' => 'GROUP', 
		'expr_type' => 'COL', 'src_col' => ['pref_value','user_pref'], }, },
	{ 'NODE_TYPE' => 'view_expr', 'ATTRS' => { 'view_part' => 'HAVING', 
			'expr_type' => 'SFUNC', 'sfunc' => 'GT', }, 'CHILDREN' => [ 
		{ 'NODE_TYPE' => 'view_expr', 'ATTRS' => { 
			'expr_type' => 'SFUNC', 'sfunc' => 'GCOUNT', }, },
		{ 'NODE_TYPE' => 'view_expr', 'ATTRS' => { 
			'expr_type' => 'LIT', 'lit_val' => '1', }, },
	] },
] } );

print $model->get_all_properties_as_xml_str();

$model->destroy();