# include	<pv.h>
# include	<ingres.h>
# include	<access.h>
# include	<aux.h>
# include	<catalog.h>
# include	<symbol.h>
# include	<lock.h>
# include	<func.h>
# include	<sccs.h>
# include	<errors.h>

SCCSID(@(#)create.c	8.7	5/1/86)

extern	short	tTdbu[];
extern	int	create();
extern	void	null_fn();

struct fn_def CreateFn =
{
	"CREATE",
	create,
	null_fn,
	null_fn,
	NULL,
	0,
	tTdbu,
	100,
	'Z',
	0
};

/*
**  CREATE -- create new relation
**
**	This module creates a brand new relation in the current
**	directory (database).  The relation is always created as
**	a paged heap.  It may not redefine an existing relation,
**	or rename a system catalog.
**
**	Trace Flags:
**		31
*/


struct domain
{
	char	*name;
	char	frmt;
	char	frml;
};

extern int   errno;
extern DESC   Reldes, Attdes;

/*
**  CREATE -- create new relation
**
**	This routine is the driver for the create module.
**
**	Parameters:
**		pc -- parameter count
**		pv -- parameter vector:
**			0 -- relation status (relstat) -- stored into
**				the 'relstat' field in the relation
**				relation, and used to determine the
**				caller.  Interesting bits are:
**
**				S_INDEX -- means called by the index
**					processor.  If set, the 'relindxd'
**					field will also be set to -1
**					(SECINDEX) to indicate that this
**					relation is a secondary index.
**				S_CATALOG -- this is a system catalog.
**					If set, this create was called
**					from creatdb, and the physical
**					file is not created.  Also, the
**					expiration date is set infinite.
**				S_VIEW -- this is a view.  Create has
**					been called by the 'define'
**					statement, rather than the
**					'create' statement.  The physical
**					file is not created.
**
**			1 -- relation name.
**			2 -- attname1
**			3 -- format1
**			4, etc -- attname, format pairs.
**
**	Returns:
**		zero -- successful create.
**		else -- failure somewhere.
**
**	Side Effects:
**		A relation is created (this is a side effect?).  This
**		means entries in the 'relation' and 'attribute' cata-
**		logs, and (probably) a physical file somewhere, with
**		one page already in it.
**
**	Trace Flags:
**		31
*/

create(pc, pv)
int	pc;
PARM	pv[];
{
	register PARM		*pp;
	register int		i;
	int			bad;
	struct domain		domain[MAXDOM];
	struct domain		*dom;
	char			*relname, tempname[MAXNAME+3];
	struct tup_id		tid;
	struct relation		rel, key;
	struct attribute	att;
	DESC			desr;
	register int		relstat;
	long			temptid;
	long			npages;
	int			fdes;
	bool			internal;

#	ifdef xZTR1
	if (tTf(31, -1))
	{
		printf("creating %s\n", pv[1].pv_val.pv_str);
	}
#	endif
	pp = pv;
	relstat = oatoi(pp[0].pv_val.pv_str);
	/*
	**	If this database has query modification, then default
	**	to denial on all user relations.
	**	(Since views cannot be protected, this doesn't apply to them)
	*/
	if ((Admin.adhdr.adflags & A_QRYMOD) && ((relstat & (S_VIEW || S_CATALOG)) == 0))
		relstat |= (S_PROTALL | S_PROTRET);
	relname = (++pp)->pv_val.pv_str;
	internal = bequal(relname, "_SYS", 4);
	ingresname(relname, Usercode, rel.relid);
	bmove(rel.relid, att.attrelid, MAXNAME + 2);
	opencatalog("relation", OR_WRITE);

	/* check for duplicate relation name */
	if ((relstat & S_CATALOG) == 0)
	{
		if (openr(&desr, OR_RELTID, relname) == 0)
		{
			if (bequal(desr.reldum.relowner, rel.relowner, 2))
			{
				return (error(DUPRELNAME, relname, 0));	/* bad relname */
			}
			if (desr.reldum.relstat & S_CATALOG)
			{
				return (error(SYSRELNAME, relname, 0));	/* attempt to rename system catalog */
			}
		}
	}
	opencatalog("attribute", OR_WRITE);

	/* initialize structures for system catalogs */
	initstructs(&att, &rel);
	rel.relstat = relstat;
	if ((relstat & S_CATALOG) != 0)
		rel.relsave = 0;
	else if ((relstat & S_INDEX) != 0)
		rel.relindxd = SECINDEX;
	
#	ifdef xZTR3
	if (tTf(31, 2))
	{
		printf("\nrel->relprim = %D\n", rel.relprim);
		printup(&Reldes, &rel);
	}
#	endif

	/* check attributes */
	pp++;
	for (i = pc - 2; i > 0; i -= 2)
	{
		bad = chk_att(&rel, pp[0].pv_val.pv_str, pp[1].pv_val.pv_str, domain, internal);
		if (bad != 0)
		{
			return (error(bad, relname, pp[0].pv_val.pv_str, pp[1].pv_val.pv_str, 0));
		}
		pp += 2;
	}

	/*
	** Create files if appropriate. Concurrency control for
	** the create depends on the actual file. To prevent
	** to users with the same usercode from creating the
	** same relation at the same time, their is check
	** on the existence of the file. The important events are
	** (1) if a tuple exists in the relation relation then
	** the relation really exists. (2) if the file exists then
	** the relation is being created but will not exist for
	** use until the relation relation tuple is present.
	** For VIEWS, the file is used for concurrency control
	** during the create but is removed afterwards.
	*/
	if ((relstat & S_CATALOG) == 0)
	{
		/* for non system named temporary relations
		** set a critical section lock while checking the
		** existence of a file.  If it exists, error return(DUPRELNAME)
		** else create file.
		*/
		temptid = 0;
		if (Lockrel && (!bequal(rel.relid,"_SYS",4)))
		{
			temptid = -1;
			setcsl(temptid);	/* set critical section lock */
			if ((fdes = open(rel.relid,O_RDONLY)) >= 0)
			{
						/* file already exists */
				close(fdes);
				unlcs(temptid);	/* release critical section lock */
				return (error(DUPRELNAME, relname, 0));
			}
			errno = 0;	/* file doesn't exist */
		}
		ingresname(rel.relid, rel.relowner, tempname);
		desr.relfp = creat(tempname, FILEMODE);
		if (temptid != 0)
			unlcs(temptid);	/* release critical section lock */
		if (desr.relfp < 0)
			syserr("create: creat %s", rel.relid);
		desr.reltid.ltid = -1L;	/* init reltid to unused */
		if ((relstat & S_VIEW) == 0)
		{
			npages = 1;
			if (i = formatpg(&desr, npages))
				syserr("create: formatpg %d", i);
		}

		close(desr.relfp);
	}

	/* insert attributes into attribute relation */
	pp = pv + 2;
	dom = domain;
	for (i = pc - 2; i > 0; i -= 2)
	{
		ins_att(&Attdes, &att, dom++);
		pp += 2;
	}

	/*
	** Flush the attributes. This is necessary for recovery reasons.
	** If for some reason the relation relation is flushed and the
	** machine crashes before the attributes are flushed, then recovery
	** will not detect the error.
	** The call below cannot be a "noclose" without major changes to
	** creatdb.
	*/
	if (i = pageflush(0))
		syserr("create:flush att %d", i);

	if (i = insert(&Reldes, &tid, &rel, FALSE))
		syserr("create: insert(rel, %.14s) %d", rel.relid, i);

	if (relstat & S_VIEW)
		unlink(tempname);
	return (0);
}



/*
**  CHK_ATT -- check attribute for validity
**
**	The attribute is checked to see if
**	* it's name is ok (within MAXNAME bytes)
**	* it is not a duplicate name
**	* the format specified is legal
**	* there are not a ridiculous number of attributes
**	  (ridiculous being defined as anything over MAXDOM - 1)
**	* the tuple is not too wide to fit on one page
**
**	Parameters:
**		rel -- relation relation tuple for this relation.
**		attname -- tentative name of attribute.
**		format -- tentative format for attribute.
**		domain -- a 'struct domain' used to determine dupli-
**			cation, and to store the resulting name and
**			format in.
**
**	Returns:
**		zero -- OK
**		5104 -- bad attribute name.
**		5105 -- duplicate attribute name.
**		5106 -- bad attribute format.
**		5107 -- too many attributes.
**		5108 -- tuple too wide.
**
**	Side Effects:
**		'rel' has the relatts and relwid fields updated to
**		reflect the new attribute.
**
**	Trace Flags:
**		31
*/

chk_att(rel, attname, format, domain, internal)
struct relation	*rel;
char		*attname, *format;
struct domain	domain[];
bool		internal;
{
	register int			i;
	register struct relation	*r;

	r = rel;

#	ifdef xZTR3
	if (tTf(31, 1))
		printf("chk_att %s %s\n", attname, format);
#	endif

	if (sequal(attname, "tid"))
		return (BADATTRNAME);		/* bad attribute name */
	if ((i = dup_att(attname, r->relatts, domain)) < 0)
		return (DUPATTRNAME);		/* duplicate attribute */
	if (formck(format, &domain[i], internal))
		return (BADATTRFORMAT);		/* bad attribute format */
	r->relatts++;
#ifndef	VAX
/* round the location up so that we don't get off address traps */
	if (domain[i].frmt != CHAR)
		r->relwid=((r->relwid-1)|((domain[i].frml&I1MASK)-1))+1;
#endif
	r->relwid += domain[i].frml & I1MASK;
	if (r->relatts >= MAXDOM)
		return (TOOMANYDOMS);		/* too many attributes */
	if (r->relwid > MAXTUP && (r->relstat & S_VIEW) == 0)
		return (RELTOOWIDE);		/* tuple too wide */
	return (0);
}




/*
**  INS_ATT -- insert attribute into attribute relation
**
**	Parameters:
**		des -- relation descriptor for the attribute catalog.
**		att -- attribute tuple, preinitialized with all sorts
**			of good stuff (everything except 'attname',
**			'attfrmt', and 'attfrml'; 'attid' and 'attoff'
**			must be initialized to zero before this routine
**			is called the first time.
**		dom -- 'struct domain' -- the information needed about
**			each domain.
**
**	Returns:
**		none
**
**	Side Effects:
**		The 'att' tuple is updated in the obvious ways.
**		A tuple is added to the 'attribute' catalog.
**
**	Trace Flags:
**		none currently
*/

ins_att(des, att, dom)
DESC			*des;
struct attribute	*att;
struct domain		*dom;
{
	register int		i;
	struct tup_id		tid;
	register struct domain	*d;

	d = dom;

	pmove(d->name, att->attname, MAXNAME, ' ');
#ifndef	VAX
/* round the location up so that we don't get off address traps */
	if (d->frmt != CHAR)
		att->attoff=((att->attoff-1)|(d->frml-1))+1;
#endif
	att->attfrmt = d->frmt;
	att->attfrml = d->frml;
	att->attid++;
	if (insert(des, &tid, att, FALSE))
		syserr("ins_att: insert(att, %s)", d->name);
	att->attoff += att->attfrml & I1MASK;
}




/*
**  DUP_ATT -- check for duplicate attribute
**
**	The attribute named 'name' is inserted into the 'attalias'
**	vector at position 'count'.  'Count' should be the count
**	of existing entries in 'attalias'.  'Attalias' is checked
**	to see that 'name' is not already present.
**
**	Parameters:
**		name -- the name of the attribute.
**		count -- the count of attributes so far.
**		domain -- 'struct domain' -- the list of domains
**			so far, names and types.
**
**	Returns:
**		-1 -- attribute name is a duplicate.
**		else -- index in 'domain' for this attribute (also
**			the attid).
**
**	Side Effects:
**		The 'domain' vector is extended.
**
**	Trace Flags:
**		none
*/

dup_att(name, count, domain)
char		*name;
int		count;
struct domain	domain[];
{
	register struct domain	*d;
	register int		lim;
	register int		i;

	lim = count;
	d = domain;

	for (i = 0; i < lim; i++)
		if (sequal(name, d++->name))
			return (-1);
	if (count < MAXDOM)
		d->name = name;
	return (i);
}




/*
**  INITSTRUCTS -- initialize relation and attribute tuples
**
**	Structures containing images of 'relation' relation and
**	'attribute' relation tuples are initialized with all the
**	information initially needed to do the create.  Frankly,
**	the only interesting part is the the expiration date
**	computation; longconst(9, 14976) is exactly the number
**	of seconds in one week.
**
**	Parameters:
**		att -- attribute relation tuple.
**		rel -- relation relation tuple.
**
**	Returns:
**		none
**
**	Side Effects:
**		'att' and 'rel' are initialized.
**
**	Requires:
**		time -- to get the current date.
**
**	Called By:
**		create
**
**	Trace Flags:
**		none
**
**	Diagnostics:
**		none
**
**	Syserrs:
**		none
**
**	History:
**		2/27/78 (eric) -- documented.
*/

initstructs(att, rel)
register struct attribute	*att;
register struct relation	*rel;
{
	/* setup expiration date (today + one week) */
	time(&rel->relstamp);
	rel->relsave = rel->relstamp + 604800L;
	rel->relfree = 0;
	rel->reltups = 0;
	rel->relatts = 0;
	rel->relwid = 0;
	rel->relprim = 1;
	rel->relspec = M_HEAP;
	rel->relindxd = 0;
	rel->reldim = 0;
	att->attxtra = 0;
	att->attid = 0;
	att->attoff = 0;
}



/*
**  CHECK ATTRIBUTE FORMAT AND CONVERT
**
**	The string 'a' is checked for a valid attribute format
**	and is converted to internal form.
**
**	zero is returned if the format is good; one is returned
**	if it is bad.  If it is bad, the conversion into a is not
**	made.
**
**	A format of CHAR can be length zero only if this
**	create was generated internally.
*/

formck(a, dom, internal)
char		*a;
struct domain	*dom;
bool		internal;
{
	int			len;
	register int		i;
	char			c;
	register char		*p;
	register struct domain	*d;

	p = a;
	c = *p++;
	d = dom;

	len = atoi(p);
	i = len;

	switch (c)
	{

	  case INT:
		if (i == 1 || i == 2 || i == 4)
		{
			d->frmt = INT;
			d->frml = i;
			return (0);
		}
		return (1);

	  case FLOAT:
		if (i == 4 || i == 8)
		{
			d->frmt = FLOAT;
			d->frml = i;
			return (0);
		}
		return (1);

	  /* note: should disallow c0 from user (but needed internally) */
	  case CHAR:
		if (i > MAXFIELD || i < 0 || (i == 0 && !internal))
			return (1);
		d->frmt = CHAR;
		d->frml = i;
		return (0);
	}
	return (1);

}
